主题:【原创】闲聊敏捷编程——测试驱动开发(一) -- 代码ABC
软件开发组织过程中需要完成两个任务:第一是让需求描述更精确,第二是让代码能够准确反映需求。现实的矛盾是需求会发生变化,而变化导致代码难以准确反映需求。于是人们引入测试和设计。测试用于验证和检查(更多的是检查)代码和需求的契合程度,设计则希望代码可以更好地适应变化。
对比传统的软件开发模型和敏捷方式我们可以看出,前者更强调第一个任务,文档、字典、审核、会议等各种过程都是为了发掘需求、描述需求和规范需求变更而做的。其代价是需求变更的反应时间变长。作为程序员的本能对变更代码有强烈的抵触,而客户则不可能在一开始就将需求描述清楚,因此在实际项目中这些软件工程的工具常常不自觉地(有时是故意的)被用作开发团队和客户扯皮的工具。这样就偏离了这些过程原本设计的目的。在保持需求稳定和代码返工的选择上大多数开发团队会本能地选择前者,虽然最终大多需要屈服。因为需求变更反映的是软件的价值。
敏捷方式选择了另一个解决方法,让代码以最大的灵活性来适应需求的变更。敏捷的一个含义就是代码的灵活性。如果我的代码可以随时修改,那么我自然不担心需求发生变更,也不太担心需求定义不准确。这样做法的代价是什么呢?我觉得最大的代价是程序员必须用一种全新的观点来看待设计和测试,对于大多数程序员来说这是一个挑战。
让代码随时保持变更的能力的一种方法就是测试驱动开发。首先测试驱动开发的过程本身就让我们不断地修改代码,即使需求是确定的。因为在开发过程中有一个原则——保持未完成的工作最大化。具体的要求是让代码只能通过已写出的测试,令下一次测试就会击败现有的代码。或者说不允许超前设计,哪怕你知道下一分钟就就需要变更需求也不要为这个变更去修改设计。其过程就像雕刻,每一刀要求不多也不少,每一刀之后再回头看看需要再哪里下一刀(测试)。在Robert C.Martin所写的《敏捷软件开发 原则、模式与实践》一书中有一个开发保龄球计分的例子就很好地说明了这个过程。一般的开发方法会在一开始就将保龄球的规则全部考虑进来,如补中、全中等等,然后以此做一个设计。然后编码,最后用一系列测试来验证代码。然而在极限编程(敏捷开发的一种)中则是每写出一个测试就写一段代码实现,和以往的开发方式最重要的区别在于实现的代码只考虑已经写出的测试而不去理会真实的保龄球计分。这就是测试驱动开发中“驱动”两字的含义。代码的变更只受测试变更的影响。这样的代码是很简单的——至少开始是很简单的。
简单的代码和简单的设计可以随时抛弃和变更——这就是敏捷的核心!而对程序员的挑战也在于此。我们——尤其是有经验的程序员总会被设计所诱惑。我们试图在一开始就写出一个包罗万象的架构,其中包含了大量可能并不需要的东西。最终的结果就是这个设计在多次变更后变得僵化,不容易修改,导致我们不愿意放弃,这也是我们抵触需求变更的基础。也许有人会反对,高手们反对会更加的激烈。因为他们知道良好的设计本来就是为了对付变更的,许多大牛可以在项目开始不久就设计出一个灵活的架构,将每个模块间的耦合降到最低,这样的架构对变更也是友好的。我承认这是事实,不谦虚地说我也是这样的人——曾经。
有一句话:把复杂的事情搞简单是一件很困难的事情,反过了则很容易。体会最深的时候就是我被敏捷思路彻底洗脑的时候。首先作为一个老程序员——或者说大龄程序员,我知道实现一个功能通常不止一种方法,每次都能在一开始找出最佳方法的概率太小了。事实上我经常在后来发现有更简单的方法,而这些简单方法基本都是在需求大部清晰的时候才发现的,而我的复杂方法得复杂性在于我过多地考虑了如何应付客户不可能发生的需求上。这些设计的复杂性常常阻止我去对现有代码进行修改,带着遗憾交付或者根本不知道存在遗憾的交付发生的次数是很多的。还有一些情况,这个复杂设计未必能完全覆盖客户的需求变更——这种情况更多,那么我们的选择是什么呢?一般来说我们只能对原有的设计进行修补而不会推倒重来,因为那意味着大量的返工。当这种修补(尤其是在进度压力下)积累到一定程度之后,我们的代码必然变得僵化再难以适应新的变更,最后就陷入了修补——僵化——修补的恶性循环中。有经验的程序员会在适当的时候对设计进行重大修改——这需要勇气。大牛们通常不缺乏这种勇气,事实上有这种勇气的人才叫大牛。因为大牛知道如何最快地完成这种修改。敏捷的模式是不需要大牛的,如果一开始就不做超前设计,而是让设计在构建过程中逐步建立反而容易形成优化的设计。在这个过程中测试驱动开发经常是推倒原有的设计——小范围的推倒,让程序员习惯对自己的代码动手动脚。在潜移默化下水准上的程序员也会具备推倒的勇气和技巧。这样我们的开发团队就逐渐具备了随时修改代码的能力,从而进化到敏捷团队。
也许有人觉得这种不断推倒重来的过程效率很低,但是别忘记再测试驱动开发中,测试是不断建立的,也就是就项目进度而言需求已经是在不断地完善,推倒的主要是一些拙劣的设计而不是项目本身。
以上我讲的范畴基本都局限在如何写代码这个过程上,很少涉及项目、客户的问题。这其实很正常,因为题目是测试驱动,这个过程主要的精力是如何写代码。在敏捷开发中代码是关键,如果不能掌握敏捷代码的编写其他敏捷思路都是空中楼阁。在写了那么多年的代码之后再来学习怎么写代码的确是一件令人汗颜的事情。测试驱动开法还有不少内容,比如我是怎么理解设计、怎么理解测试和进度的关系等等。真是一个没完没了地话题。
本帖一共被 1 帖 引用 (帖内工具实现)
- 相关回复 上下关系8
🙂讲测试驱动开发,怎么不把常用的测试工具列上? 素里太守 字22 2009-06-11 00:16:47
🙂晕 白兔大牙 字106 2009-06-09 03:40:34
🙂我也晕 代码ABC 字82 2009-06-09 06:42:48
🙂测试驱动开发(二)
🙂TDD虽然不错,实际做下来还是很多问题 15 风北客 字2038 2009-06-09 08:07:26
🙂个人的一些意见 4 代码ABC 字1228 2009-06-09 08:41:02
🙂对程序员来说,不能过分强调测试驱动, 木头人 字212 2009-06-08 21:10:44
🙂对于xp来说,全覆盖的测试是不可能,也是不需要的 风北客 字242 2009-06-09 07:43:02