国内的一个很流行的博客上面讨论了TDD的一些问题,并且顺带批评了某咨询公司的咨询师不够脚踏实地。我在那个博客留言表达过我的不同意见,前几天随着另外一篇对TDD质疑的文章发表我和文章的作者在Twitter上论战了一番,不过论战是不能解决问题的(之前InfoQ上的虚拟讨论也没有解决问题),所以我再简单的整理一下我的意见。
首先,我声明我是一个笃信极限编程对『编程』有巨大价值的人。而TDD是极限编程XP里面的实践之一,在讨论XP的过程中我们一般都倾向于使用『实践(practice)』这个词,而不是『方法论(methodology)』,原因是极限编程描述的这些『方法』实际上是很多做事的具体方式,而不是一种『理论』。
我不应该咬文嚼字,不过有些时候这很重要。我需要从极限编程这个名字开始讨论这个问题。以前的一次OpenParty上,o6z问我『你知道极限编程为什么叫极限编程么?』,我当时真的不知道答案,只是隐约觉得极限编程的很多个体实践都是一种极限的挑战。而后o6z说『其实极限编程就是指程序员公认一些最佳实践,他们致力于不断的改进、优化这些最佳实践,最后把他们推向极致,这就叫极限编程』。这个答案可能是官方的,也可能是坊间流传的,不过我觉得它特别贴切,解释了极限编程的价值观。我当前公司的CEO上次调侃过『价值观』这个词,他说价值观说出来就不灵了,就成狗屁了……他说真正的价值观体现在你做每个决策的时候左右你的那种抽象的直觉。极限编程的每个写下来的『实践』其实就是写下来的价值观的体现,在实践的过程中最重要的就是不断的去磨练你的直觉,让价值观内在化。OK,极限编程我说道这里。
TDD,测试驱动开发。我们按照刚才阐述极限编程的涵义推演一下TDD是怎么来的。我们在写程序的时候发现不对程序进行细粒度的验证就很容易产生Bug,逐渐的整个编程社区有了一个最佳实践『单元测试』,我们也知道『单元测试』是相对于『集成测试』和『系统测试』的,我们提高程序内在质量的时候这些测试工具我们都要使用,只是有时其它测试会通过那种被叫做QA的程序员编写。为了不扯到另外一个话题,我们继续,当大家公认测试是最佳实践的时候,极限编程社区把测试推向极致。极致的测试应该具有细的粒度,高的覆盖率,有意义的验证条件,全面的边界条件,不脆弱等等,这些指标单个都不产生价值,但是在某个平衡的状态它具有最高的价值。极限的TDD的目的就是找到那个平衡。
我们退一步说,其实目前我们的程序员社区绝大部分人面临的都不是是否可以做好TDD,找到那个最佳的平衡点的问题。现在的主要问题还是是否可以写出有意义的测试,如何写测试的问题。其实质疑TDD的朋友经常的理由是『只要写好单元测试就可以了』,这其实正是我现在说的我们大多数程序员的困惑。也就是说『使用TDD』的对面是『能够写好单元测试但是不做TDD』,我认为这是一个伪命题。因为TDD的目的就是把测试这种最佳实践推向极限,这是一个过程,我可以把它分成两个阶段:
- 第一个阶段是通过TDD强制从不写测试向写测试转变,因为绑定了写测试和写代码的节奏,它可以保证你写出的代码是可以测试的;
- 第二阶段是通过不断实践和优化TDD让你能够写好测试。因为测试不只是有单元测试,还有系统测试和集成测试,随着对TDD的熟悉你会发现可以用不同层级的测试来驱动你的设计。使用越高层级的测试越有难度,这也是极限的一种体现。现在比较普遍的BDD其实就是将领域模型驱动DDD这种建模的方式与TDD结合的产物, Spec的描述形式让它不仅可以组织好单元测试,也可以组织好系统测试(如验收测试驱动设计,ATDD)。第二个阶段可以一直优化,永远没有极限,这个过程是最有价值的;
这种阶段化的实践其实内置了一些积极的意图:
- 先写测试后写实现其实是把同时写测试和实现更近一步的产物。同时写测试的一种形容就是『可测试性驱动程序开发』,前几年老赵就写过这方面的博文,并且从可测试性上论证了TDD的积极意义。因为只有同时写测试和实现才能最好的保证你的代码是可测试的。而解决可测试性的难题的关键点是有可以检查验证条件(而不是足够细的粒度,细粒度既不是充分条件也不是必要条件,只是细粒度容易找到验证条件而已),也就是测试结果需要可验证。如果没有已知的可检查验证条件,那么可测试性就无法保证。所以先构思一个测试验证点再写代码是这个逻辑的体现。
- 测试准备耗时费事,但是如果我们偷懒不做,那么后期可能面临的是完全无法做。保证测试容易准备需要在设计上多加考虑,如慎用Singleton等。这些思考可以影响你的设计,帮助你着力思考系统中的哪些状态是可变的,哪些是不变的,帮助你强化设计出无副作用或者少副作用的代码(因为有副作用的代码更难准备测试环境,环境的组合会多变),这帮助你函数化思考。
- 红绿的节奏和小步前进可以帮助你减少对调试的依赖。调试和测试都是我们验证Bug的工具,不过我们最好在难以复现的场景使用调试这种终极武器,在编码阶段反复的进入调试说明你的测试有问题。编码阶段调试的常见原因就是测试没有跟上,因为我们知道最最常见的代码错误就是拼写错误。测试和调试找到拼写错误的代价是完全不同的。小步前进配合现代版本控制工具可以让我们完美的通过折半查找找到出问题的代码所在,如果有自动化测试套件的话折半查找还会事半功倍。这一条是说TDD的节奏所鼓励的小步前进的好处。
- 测试驱动的测试需要有一个明确的名字。在寻找名字的过程你会重新思考这条验证的目的,让你整理需求的思路,也就是提醒你经常的问『为什么?』。细粒度的问为什么,并且为这些需求设计测试场景,这对每个想要贯彻『具体问题具体分析』的程序员非常重要。
这样的王婆卖瓜的理由我还有很多,多说无益。其实这里有一个关键的问题需要澄清,我们说TDD有这么多积极的意义,但是我们不能说『所有的代码都要TDD』,因为它很容易让TDD成为不现实的『生产力毒药』。当初比我经验丰富的一位同事就和我说『Spike(技术验证)的时候不需要TDD』,我发现在需要自由翱翔的时候放弃TDD的确是很舒服的事情,不过每当我们看这些Spike产生的代码时我们会发现TDD的重要性。因为这些 Spike出来的代码经常惨不忍睹。另外一位资深的同事就又和我说『Spike完成后,你应该删掉那些代码。然后重新TDD去实现它们,因为严格测试、精心设计过的代码才是为生产环境准备的』。我举的这个例子不能绝对化,不过我想表达的是,如果你想知道不做TDD的后果,那么一定要先做好TDD,回去对比观察没有TDD代码的不是。大部分关于TDD的批评大都来自那些还没有完全掌握TDD的人,工作流还没有很好的优化,此时对比『写单元测试』的自己就开始觉得TDD让自己混身不适了,『把最佳实践推向极限这个行为』要在完全掌握『最佳实践』的前提下才可以继续修炼。
我这里放一个比喻,我不知道是否贴切:有一种修行是爬看不到顶峰的山,视力可及的半山腰上有一片开满鲜花的平台。有些人爬到平台就下结论『爬到山顶也不过如此,也许还没有这么多鲜花呢』。但是另外一些人则继续攀登,以致山下都看不到他们的身影了。半山腰的人也许会开始质疑那些继续攀登的人的动机,说这完全是一种宗教。对于继续攀登的人来说,的确是一种信仰让他们坚持下去,那就是极限编程。关键的问题在于,对于那些山脚下的『沉默的大多数』人而言他们应该听谁的呢?是应该相信半山腰的人说『那些持续攀登的人走火入魔了,其实半山腰这里就最好了』?还是跟随那些持续攀登的人所走过的路走下去?其实,持续攀登的人会告诉所有山下的人你随时可以转身回到那个平台去,所以我们的行为是安全的,不过如果你持续攀登,那山上一定有一个更好的世界。有些人听了前者的话停留在山下徘徊,因为他们绝得那半山腰也不过如此,山下的日子很好过。但是有些人听了后者的话爬上了半山腰,还有一些也成为了后者。
这种比喻可以写的很华丽,不过这不是什么论据,它只是一种修辞而已。《思考的技术》这本书的第二章叫「逻辑打动人心」,我摘一些句子:
“但是”、“然而”这种话,对于改善经营而言,有白害而无一利
这是借口中常用的词,它会给出一个反面的评价,而后是中庸的『具体问题具体分析』。极限编程不是这样,它是单向的夸张,把最佳实践推向极限。所谓『写好单元测试就好了,要具体问题具体分析』其实是不写测试的一个好借口。如果你想让自己积极的去实践,请给自己一个极限的理由,当然,你随时都可以转身回到那个平台的。
如果给客户的药方,只是没有什么感觉的营养剂,客户的经营状况将无法改善
这是我想说明极限编程的极限的意义。这些推向极限的过程不是邪教,让你以为最后可以看到神迹而葬身途中,它的目的是给你一剂猛药,让你更好的走到那个平台,甚至到下一个层次上去(第一个平台的人兴许都不知道后面还有其它平台吧?)。
其实说到这里咨询公司的秘密也揭开了。咨询公司的医生不是包治百病的,他们的大部分都是希望给你一个最佳的『盗梦空间中的术语,植入想法,Inception』,期望这个植入可以帮助你向积极的方向前进,不过你的行为依然是你自己控制的。好的咨询公司不会利用这个机会『洗脑』,因为他们自己也在不断追求极致的过程中。这更像把酿造啤酒的技术推向极致的修士们(Trappist,非常著名的修道院啤酒的修士们通过几代人不断的优化酿造他们认为完美的啤酒,具有稳定而微妙的口味)所做的修行,他们自己在不断优化自己所做的事情,并通过咨询把这些Inception植入客户的思想。这里还要澄清一个问题,不是修道院中的所有修士都有崇高理想,有些新进来的修士不会酿酒,有些隐藏在修士中的南郭先生可能没有追求极致的精神,这非常正常,我们都是不完美的,我们的组织也不会完美,不过我们依然可以有追求完美的组织。
写到这里,我重复一下我在OpenParty的朋友中经常说的一句话『要把积极的影响施加给身边的朋友们』,我不像Cleverpig信仰巴哈依教,不过我坚持积极做人,积极影响人。极限编程和其中的TDD都出于同样的动机,施加给所有的程序员积极的愿望,不断的优化自己的工作流,以期达到最终的『极致』。谢谢观赏。
内文的一些链接我会稍后添加
后记:最近工作家里都忙,有了一个小公主需要伺候,所以没有太多时间更新Blog。我写博客不是让人围观的,而是写给自己和我所爱的家人朋友。我在Twitter上口水战还有写这篇博文主要是『质疑TDD和某咨询公司』与我的价值观冲突,所以我才不得不写这篇博文表达我的观点。我和我的朋友们还在努力组织好OpenParty的Unconference活动,我们的理想主义可以在这个活动上得到满足,我们给大家一个自由的分享与获取知识的机会,并且更重要的是我们要把积极的态度植入到参与活动的朋友的意识中,我想这是让我们生活更美好的最佳途径。