TDD,测试代码可以代替文档吗?
曾经,我认为只要做好详细设计工作,软件编码就成为一种体力活。在我印象中传统软件工程理论好像是这么说得:分析和设计是软件生产过程中最重要的两个阶段,好的设计产生好的结果,坏的设计产生坏的结果,详细设计文档是软件过程中最重要的部分,甚至比代码还重要。国内某人的书中还提到,“只要有了详细设计,哪怕原来的开发人员都离开了,换一批人照着详细设计仍然能把软件做完”。一提到详细设计我的脑子里也已经出现了这样的影子:长长的(或者厚厚的)文档,详细到每个函数,甚至是每个函数参数的名字都定义好了,用这样的详细设计指导代码编写应该是一件多么惬意的事情啊。我推崇这种事无巨细的详细设计,认为只要是设计好就能够适应变化,并把软件项目的失败归咎与设计人员的知识、能力或经验不足。这种想法持续了很长时间,直到我有了实际软件项目的经验并开始单独做设计为止。
促使我思想转变的原因就是两个字:变化。我原来也知道变化对设计的影响,但是还是低估了变化对设计带来的冲击。现实中的详细设计只是一个看上去很美的东西,开始编写代码一个月后的详细设计就基本上不能指导代码编写了,甚至变成和实际代码完全两回事的东西,成了一堆废纸,原因还是那两个字:变化。是的,变化,在某个时间已经很缜密的设计,在下个时间就会变得漏洞百出,因为计划赶不上变化,通常情况下,详细设计文档从其完成的那一刻起就开始散发出“腐败”的味道。只有没有任何软件开发经验的人才会天真地认为一次做好完备的详细设计,然后就可以在其指导下完成软件开发,最终得到产品。
在传统的软件过程中,面对逐渐散发出“腐败”味道的设计文档,通常有两种对策,一种是安排专职的文档开发人员,每次在代码中修改设计都及时更新到设计文档中,以保持文档的“新鲜”。其实这种方法也只是一种看上去很美的东西,且不说多数项目组都不会有多余的人手专职做文档开发(有哪个项目组敢在项目还在进行中就说自己的人手足够了?),就算有这样一个文档开发人员,那么是否每次设计上的小修改都会通知到他(她)呢?显然不会,他(她)必须Review每一个开发人员的代码,并与大家随时沟通,发现与原始设计不一致的修改,这样会累死人的。那么是否可以由开发人员自己负责维护与自己工作相关的那部分文档呢?试想一下,当轰轰烈烈地代码编写开始后,或者头顶着产品交付倒计时牌,开发人员是否还有“闲情逸致”时不时停下正在Coding的手去重新修改一下设计文档呢?另一种对策是任由设计文档慢慢“腐败”,把主要力量集中在代码上,毕竟最终交付产品依靠代码而不是设计文档。这种情况下原来花费大量时间完成设计文档纯粹是浪费时间,哪怕是对自己人也没有丝毫用处,如果我是这个项目组中补充进来的新人,看到这样的文档和那样的代码,可能会得精神分裂症。
所以在敏捷(Agile)的词汇表里已经没有详细设计这个词汇了,取而代之的是“够用设计”,就是所谓的Not less not more, just enough。那么在敏捷这种短迭代周期,快速反馈体系中,是否还有必要编写一份用自然语言或图表展示的详细设计文档呢?Jack W.Reecves在他的论文《什么是软件设计?》中提出了“源代码就是设计文档”的观点,Jack说得设计文档和我本文提到的设计文档还不是同一个概念,我把他的观点引用在这里主要是为了和另一个观点做对比。Jack认为,“任何工程活动的最终目标都是某些类型的文档。当设计工作完成时,设计文档就被转交给制造团队。该团队是一个和设计团队完全不同的群体,并且其技能也和设计团队完全不同”【1】,由此看来,如果源代码不是文档,那么软件开发人员就不能被称为是工程师了,他们就和建筑工地上的工人一样,只是一群Builder。是否可以用源代码代替任何用自然语言描述的文档呢?我看还不太可行,源代码相对于自然语言来说过于晦涩,只有开发人员能够读懂(即便是开发人员,也不是所有人都能理解所有代码),显然制约了源代码作为文档的用途。不是有人在研究用自然语言编程序吗,如果这样的类似自然语言的源代码作为文档,倒是很合适。
上文提到,有一个与之对比的观点,那就是用测试代码代替源代码作为设计文档,但是前提条件是:使用TDD作为开发模式。很显然,源代码产生以后做单元测试用的测试代码已经失去了作为设计文档的前提,那就是每次迭代的设计文档必须早于或不晚于本次迭代的源代码产生出来。因为作为单元测试的测试代码是为已经存在的源代码量身定做的,并且已经受到这些代码的思维定势影响,失去了“设计”的意义。做为测试先行的TDD本身就是一个分析、设计、实现的过程,而测试代码又是这个过程的产物,测试代码理所当然的就是设计文档了。加之TDD要求在编写任何代码之前要先完成测试用例,这就是说任何新代码(新加的模块)都有配套的测试代码,自然而然的,测试代码就成为了新代码的描述文档,其中包含了如何建立使用新模块以及期望达到什么样的效果,而这正式设计文档的主要功能。源代码不适合取代自然语言的文档,因为源代码过于功能细化,逻辑纷繁复杂,分支和跳转加剧了理解代码的困难,缺乏对整个模块的宏观表达能力。与源代码相比,测试代码显然“单纯”很多,测试代码侧重于使用源代码,通常可以用作源代码的一份sample,所以它对整个模块的宏观表达能力要强于源代码。另外,测试代码通常是线性结构,比较容易看懂,从可理解性上看,测试代码比源代码更接近自然语言,因此测试代码比源代码更适合作为软件的设计文档。
那么测试代码是否可以完全取代自然语言形式的设计文档呢?我看还不行,原因有三:
其一,测试代码虽然比源代码容易理解,但它仍然是代码,不是所有人都能理解的;
其二,测试代码的宏观表达能力还是不如自然语言或图表;
其三,很多人习惯看文字而不是看代码,彻底改变人的习惯很难。
所以在TDD开发过程中,比较好的形式是自然语言的文档和测试代码相结合,用自然语言的文档做一个够用的设计就行了,这个设计只要详细到模块关系这一级别就足够了,各个模块的详细设计就由测试代码充当。
不管是“源代码就是文档”的观点还是“测试代码就是文档”的观点,目的都只有一个,那就是消除那些浪费人力物力做出来的、没用的、总是散发着“腐败”味道的东西。
[1] Jack W.Reecves. 什么是软件设计. 2002.
促使我思想转变的原因就是两个字:变化。我原来也知道变化对设计的影响,但是还是低估了变化对设计带来的冲击。现实中的详细设计只是一个看上去很美的东西,开始编写代码一个月后的详细设计就基本上不能指导代码编写了,甚至变成和实际代码完全两回事的东西,成了一堆废纸,原因还是那两个字:变化。是的,变化,在某个时间已经很缜密的设计,在下个时间就会变得漏洞百出,因为计划赶不上变化,通常情况下,详细设计文档从其完成的那一刻起就开始散发出“腐败”的味道。只有没有任何软件开发经验的人才会天真地认为一次做好完备的详细设计,然后就可以在其指导下完成软件开发,最终得到产品。
在传统的软件过程中,面对逐渐散发出“腐败”味道的设计文档,通常有两种对策,一种是安排专职的文档开发人员,每次在代码中修改设计都及时更新到设计文档中,以保持文档的“新鲜”。其实这种方法也只是一种看上去很美的东西,且不说多数项目组都不会有多余的人手专职做文档开发(有哪个项目组敢在项目还在进行中就说自己的人手足够了?),就算有这样一个文档开发人员,那么是否每次设计上的小修改都会通知到他(她)呢?显然不会,他(她)必须Review每一个开发人员的代码,并与大家随时沟通,发现与原始设计不一致的修改,这样会累死人的。那么是否可以由开发人员自己负责维护与自己工作相关的那部分文档呢?试想一下,当轰轰烈烈地代码编写开始后,或者头顶着产品交付倒计时牌,开发人员是否还有“闲情逸致”时不时停下正在Coding的手去重新修改一下设计文档呢?另一种对策是任由设计文档慢慢“腐败”,把主要力量集中在代码上,毕竟最终交付产品依靠代码而不是设计文档。这种情况下原来花费大量时间完成设计文档纯粹是浪费时间,哪怕是对自己人也没有丝毫用处,如果我是这个项目组中补充进来的新人,看到这样的文档和那样的代码,可能会得精神分裂症。
所以在敏捷(Agile)的词汇表里已经没有详细设计这个词汇了,取而代之的是“够用设计”,就是所谓的Not less not more, just enough。那么在敏捷这种短迭代周期,快速反馈体系中,是否还有必要编写一份用自然语言或图表展示的详细设计文档呢?Jack W.Reecves在他的论文《什么是软件设计?》中提出了“源代码就是设计文档”的观点,Jack说得设计文档和我本文提到的设计文档还不是同一个概念,我把他的观点引用在这里主要是为了和另一个观点做对比。Jack认为,“任何工程活动的最终目标都是某些类型的文档。当设计工作完成时,设计文档就被转交给制造团队。该团队是一个和设计团队完全不同的群体,并且其技能也和设计团队完全不同”【1】,由此看来,如果源代码不是文档,那么软件开发人员就不能被称为是工程师了,他们就和建筑工地上的工人一样,只是一群Builder。是否可以用源代码代替任何用自然语言描述的文档呢?我看还不太可行,源代码相对于自然语言来说过于晦涩,只有开发人员能够读懂(即便是开发人员,也不是所有人都能理解所有代码),显然制约了源代码作为文档的用途。不是有人在研究用自然语言编程序吗,如果这样的类似自然语言的源代码作为文档,倒是很合适。
上文提到,有一个与之对比的观点,那就是用测试代码代替源代码作为设计文档,但是前提条件是:使用TDD作为开发模式。很显然,源代码产生以后做单元测试用的测试代码已经失去了作为设计文档的前提,那就是每次迭代的设计文档必须早于或不晚于本次迭代的源代码产生出来。因为作为单元测试的测试代码是为已经存在的源代码量身定做的,并且已经受到这些代码的思维定势影响,失去了“设计”的意义。做为测试先行的TDD本身就是一个分析、设计、实现的过程,而测试代码又是这个过程的产物,测试代码理所当然的就是设计文档了。加之TDD要求在编写任何代码之前要先完成测试用例,这就是说任何新代码(新加的模块)都有配套的测试代码,自然而然的,测试代码就成为了新代码的描述文档,其中包含了如何建立使用新模块以及期望达到什么样的效果,而这正式设计文档的主要功能。源代码不适合取代自然语言的文档,因为源代码过于功能细化,逻辑纷繁复杂,分支和跳转加剧了理解代码的困难,缺乏对整个模块的宏观表达能力。与源代码相比,测试代码显然“单纯”很多,测试代码侧重于使用源代码,通常可以用作源代码的一份sample,所以它对整个模块的宏观表达能力要强于源代码。另外,测试代码通常是线性结构,比较容易看懂,从可理解性上看,测试代码比源代码更接近自然语言,因此测试代码比源代码更适合作为软件的设计文档。
那么测试代码是否可以完全取代自然语言形式的设计文档呢?我看还不行,原因有三:
其一,测试代码虽然比源代码容易理解,但它仍然是代码,不是所有人都能理解的;
其二,测试代码的宏观表达能力还是不如自然语言或图表;
其三,很多人习惯看文字而不是看代码,彻底改变人的习惯很难。
所以在TDD开发过程中,比较好的形式是自然语言的文档和测试代码相结合,用自然语言的文档做一个够用的设计就行了,这个设计只要详细到模块关系这一级别就足够了,各个模块的详细设计就由测试代码充当。
不管是“源代码就是文档”的观点还是“测试代码就是文档”的观点,目的都只有一个,那就是消除那些浪费人力物力做出来的、没用的、总是散发着“腐败”味道的东西。
[1] Jack W.Reecves. 什么是软件设计. 2002.