引子
我从12年开始接触TDD,经历过一人操练的窘境也见证过团队推广的浪潮,这么多年过去后,我依旧发现周围人对于TDD仍然充满了各种各样的不适应,同时伴随着一个十分有趣的现象,培训时大家多少总能写一点TDD,可一到实际工作就消失的无影无踪。因此我试着分析其中原因,它也许并不能帮你克服你对TDD难用的看法,但它会告诉你的TDD为什么如此的难。
难在哪
上面提到的状况让我十分困惑,为了弄明白为何会出现这种状况,我和不少人谈论过,大家提及了不少困难:
- 一看到需求就想到实现,要趁热赶紧写下来,不然会忘;
- 需求知道怎么实现,可不知道怎么写测试;
- 时间紧、交付压力大,TDD太浪费时间;
- 需求变化太快,修改都来不及,根本没有时间来写TDD;
上面每一条原因讲的都是事实,在实际工作中我们面对的场景要比一次两次的TDD培训复杂的多,在各种限制和约束面前落地TDD确实不易,更多的时候我们遭遇的是不会TDD的尴尬和不敢TDD的无奈。
为何不会
不会TDD并非不了解TDD,下面这张TDD流程图我相信只要是参加过TDD培训的人都说上一二,可真正运用起来恐怕不少人会抓耳挠腮,所以“不会”说的是没有办法在面对实际工作时落笔。
经典TDD流程其实认真思考上面这张图后,就能发现真正的问题远在此图之外。图上描述的第一步是先写测试,光是这一步就能让80%的人选择放弃,再让剩下的人中10%走上歧路,因为大多数人不会写测试,或者更确切地说是不知道要测什么以及测试的内容该如何表达。TDD中的关键是T(esting),T写不好是无法驱动出产品代码的,而要产生T就需要在一切开始之前进行分析、提炼和抽象,要考虑如何在测试中“使用”产品代码,是一个实例方法还是一个类方法,是由构造函数传参还是通过方法调用传参等,这就是在做设计。要是没搞清楚如何“使用”,那十有八九就会误入歧途或者早早夭折。
之前我作培训时就遇到过由于没想明白如何“使用”,而导致测试代码怪异并且产品代码复杂难写的一个活例子。培训题目是实现一个海战游戏,其中一个需求是在战舰的一次攻击后报告攻击结果,并给出所有战舰的受损情况,结果参加培训的所有人均没有充分理解这个需求,特别是“报告结果,给出受损情况”,在没有进行提炼和分析的情况,直接在测试代码中打印了所有信息,怪异的测试带来的抽象缺失又导致产品代码中游戏棋盘的实现错误地选择了复杂的数据结构,造成艰涩难懂的语句遍地都是。
万事开头难,TDD的难在于先行的测试不是干巴巴的代码而是经过分析、会指引产品代码实现的向导,所以开始之前的分析、建模是不能少的,必要的抽象和提炼是不能缺的,也许你会说“这些我都不太会啊,怎么办?”,没关系,你不会总有人会把结对用上会有不错的效果,或者利用实例化需求,通过集体的头脑风暴再借助QA的测试思维可以把场景、流程梳理的非常清楚的,这些用例就可以作为你切入点。
为何不敢
在不少人看来“时间是程序员最大的敌人,而变化的需求是最大帮凶”。我们总在抱怨没有太多充裕的时间去进行TDD,不敢把时间分配给TDD,害怕影响产品代码开发,可实际上不考虑或不写测试而去实现产品代码从逻辑上讲是有悖生活常识的。生活中我们都是先有一个目标,即先回答“我想要什么”或“我想达到什么效果”,再去考虑要实现我该怎么样去做,很少有人是骑驴看唱本——走着瞧的。可到了编程时,鲜有人理解“意图式编程”,总习惯先实现一个东西,再去调用它,然后就是调试、返工,再调试、返工周而复始,如果按照这样的工作节奏,确实时间有点不够用。
扭转这种想法的关键是意识到“延迟”在这件事上对时间的影响。需求总是能实现的,但在先实现产品代码的过程中是震荡收敛的,要经历调试返工的“延迟”;而先写测试带来的确定的目标、简单实现、所见即所得的反馈无一不在减小“延迟”。所以“快就是慢”其实是我们自己为自己设下的陷阱,并一次次的掉坑之旅后心有余悸地总结出“即使我能很快地实现产品代码,也得花好多时间来调试”的扭曲经验。
网友评论