该问题的上下文是源于镶金玫瑰TDD:
"镶金玫瑰"!这是一家魔兽世界里的小商店。出售的商品也都是高价值的。但不妙的是,随着商品逐渐接近保质期,它们的价值也不断下滑。你需要开发一个IT系统来更新库存信息。
商品的价格规则说明如下:
- 商品的价值永远不会小于0,也永远不会超过50
- 商品每过一天价值会下滑1点 ,一旦过了保质期,价值就以双倍的速度下滑
- 陈年干酪(Aged Brie)是一种特殊的商品,放得越久,价值反而越高
- ......
今天,在TDD训练营中有学员提出在做业务需求Tasking的时候,商品价值小于0和大于50的时候怎么办?仔细想想这个问题,他问的其实不是业务需求,因为业务里没有提到这一点。他问的实际上是在代码中初始化商品的时候传入了一个负数或大于50。
回到业务需求Tasking的阶段,当你只关注业务需求的场景的时候,可能就不要考虑了,而当下要考虑的是各种不同类别的商品随着时间变化而变化的规律,以及到达0和50的边界之后的变化规律。所以你所关注的系统核心端到端的功能是按天更新。对于商品创建时候所传入的价值超出范围的问题,你在这个阶段可以不必关注。
当你将按天更新的业务场景都覆盖实现之后,系统的核心业务几乎完成,你可能会得到一个这样的Item类:
public class Item {
protected String name;
protected int sellIn;
protected int quality;
public Item(String name, int sellIn, int quality) {
this.name = name;
this.sellIn = sellIn;
this.quality = quality;
}
}
此时你发现:“咦,这个构造器传入的参数没有做任何限制呀,万一传进入-1和100呢,那岂不是有Bug?”
很好,当你写完代码之后,发现这个构造器是脆弱的,没有任何防范,此时你也可以再补两个单元测试,测试构造器中传入参数不合法时情景,但要清楚这个测试跟之前的Task驱动的测试不是为了同一个目的,但它也是有价值的。
还有一种做法是,你识别出来了,但在这啥也不做。如果在这里啥也不做,那个检查始终的有地方要做,把它当做错误在时机很晚的时候告知用户肯定不是一个好主意。
那在什么时候做呢?这需要来到架构层面去思考了。业务复杂到一定程度,大多数系统都会做架构设计,最典型的是分层,比如:六边形架构,传统三层架构,分层架构,整洁架构。这些架构采用的一个理念是将核心业务规则保护起来,跟展示层和持久层隔离开。这样做的好处是,我的业务层是不受任何外界干扰的,不管你是Web界面还是GUI输入,我只需要通过不同的适配接口去适配不同的端,我能始终保持核心业务是不受破坏的。
举个例子,当你去游乐场游玩时候,你首先要买一张门票,在门口凭票进去后,你去各个游玩项目的时候,每个项目的管理人员是怎么接待你的?当你来到过山车项目,工作人员会将你和其他3个人分成一组,然后去分配给别人。试想,当你和其他3个人兴匆匆走到车前,正要准备上车,工作人员一把喊住你:“嘿,小伙子票买了没有,让我看看你的票。” 而你的票已经存到包里,放到储物柜了,你作何感想?但通常游乐场内部的各个项目工作人员不会做去检查门票的,这个事情的职责交给了大门口的关闸,你只要通过了那个关闸,后续每个项目只用关注自己的核心业务逻辑(如何按照男女、年龄分组)。
这其实就是一种分层思想,分层的本质就是职责分离。经过这样的分离,你的核心业务逻辑只用关注自身的业务规则。对于镶金玫瑰,要想想我们要实现的是什么?我认为是核心业务规则,这个系统要投入使用,肯定是集成用户接口的,比如,商品老板在手机界面录入商品,这个时候老板输入了一个价值100的非法值,那个非法值的校验就会在用户数据的接收处就做了,传到核心业务层的数据就一定是合法的数值,这样核心业务层只用关注自己的业务规则即可,做到职责分离。