<<重构>>里介绍的坏味道,都是直接去嗅代码. 不过既然我们采用TDD,那么代码的一切坏味道,都会反应在测试上,比如一个函数的测试用例组合太多,可能意味着函数职责过多. 这在物理上称为对偶性,即不同的表达方式,反应的是同一件事. 两个非常不同的理论精确的描述了同样的现象. 分别让一对夫妻给你讲他们的故事,他们的说法会不同,但每个重要事件都能相互得到印证。和他们谈话多了,你就能指出两人说的故事有什么不同和联系。例如,丈夫觉得妻子过于自信,这正好印证了妻子抱怨丈夫太懦弱。我们可以说,两个人的话是互为对偶的。换句话讲,我们可以通过观察测试而不是直接去看代码,来发现代码的坏味道.
以上是题外话,现在进入正题
今天在面向对象训练营的培训中,发现了一种新的坏味道. 事情是这样的: 学员在完成按空位绝对数量多少停车的时候,使用了继承,SmartBoy继承自Boy. 停车是不同的,override; 而取车是一样的,代码都在基类里. 而在写测试的时候,我发现他们分成了两派. 一派没有对SmartBoy的取车进行测试,而另一派做了测试. 组织他们讨论,两派的理由都有道理: 前者说取车代码只有一份,前面Boy的测试已经测过了,因此SmartBoy可以不测; 而后者说从功能上SmartBoy和Boy完全是独立的,测试用例应该从功能分解下来,因此应该测. 而你只是内部实现使用了继承,要是我现在不测,哪一天你不用继承了,岂不是有破坏现有功能的风险?
我认为两者都有道理. 且不说可否从单元测试和功能测试的角度来解释(况且前面我们已经说了Unit Level Fucntional Test),单是这种争论的存在我们就可以思考: 是什么让我们陷入了可测可不测,进退两难的境地?
几秒钟之后我想清楚了,这是一种坏味道,原因是这里有重复. 这里虽然没有重复的代码,但是有重复的概念. SmartBoy根本就不是一个独立的概念. 同样的Boy加不同的选车位的策略才是更基本的概念. 重构到策略模式至少是一种可以消除争论的一种方案,因为此时取车只有一份了,无论从代码上还是概念上. 因此前面Boy的测试已经测过了,我们根本不需要再写一遍
推而广之,我们可以重新发现一种已知的坏味道: 实现继承. 而可有可无的测试和实现继承在坏味道方面是对偶的.