高质量的实践方法是那些能创造高质量软件的程序员的共性。这些高质量的实践方法在项目的初期、中期、末期都强调质量。
如果你在项目的末期强调质量,那么你会强调系统测试。当提到软件质量保证的时候,许多人都会想到测试。但是测试只是完整的质量保证策略的一部分,而且不是最有影响的部分。测试是不可能检查出诸如“制造了一个错误的产品”,或者“使用错误的方法制造正确的产品”之类的缺陷的。这样的缺陷必须在测试之前解决——更确切地说是在构建活动之前。
如果你在项目中期强调质量,那么你会强调构建实践。这些实践是本书绝大部分篇幅的关注点。
如果你在项目的开始阶段强调质量,那么你就会计划、要求并且设计一个高质量的产品。如果你用为Pontiac Aztek做的设计来开始整个生产过程,那么你可以想尽办法来测试,它也绝对不会变成劳斯莱斯。也许你能造出最好的Aztek,但如果想要的是一辆劳斯莱斯,那么你就得从头开始做计划。在软件开发中,你也需要在定义问题、定下解决方案的规格,以及设计解决方案的时候做出这种计划1。
由于构建活动是软件项目的中间阶段,在你开始构建的时候,项目前期工作已经或多或少为这个项目的成功或失败打下了基础。然而,在构建过程中,你至少应该能辨明当时的形势如何,如果你看到失败的乌云已经出现在地平线上时,就退回到项目的前期工作吧。本章的其余部分将仔细讲述为什么合适的准备工作是非常重要的,并且告诉你如何判定“是否已经准备好开始构建工作了”。
Do Prerequisites Apply to Modern Software Projects
前期准备适用于现代软件项目吗
有些人断言,诸如架构、设计及项目规划等前期工作对于现代软件项目来说是毫无用处的。总体来说,没有哪项研究(无论过去还是现在)支持这一断言,最近的数据也不支持这一断言(具体请看本章的其余部分)。反对前期准备的人通常会给出一些前期准备做得很糟糕的例子,然后指出这种工作毫无作用。但无论如何,前期准备也可以做得非常好;并且从20世纪70年代开始至今的业界数据显示,如果在开始构建活动之前认真地进行适当的准备活动,那么项目将会运作得极好。
准备工作的中心目标就是降低风险:一个好的项目规划者能够尽可能早地将主要的风险清除掉,以使项目的大部分工作能够尽可能平稳地进行。目前,软件开发中最常见的项目风险是糟糕的需求分析和糟糕的项目计划,因此准备工作就倾向于集中改进需求分析和项目规划。
构建活动的准备工作不是一门精密科学,要根据每一个项目的特点来选择特定的降低风险的方法。具体细节随项目的不同,会有非常大的变化。更多的信息请参阅第3.2节。
Causes of Incomplete Preparation
准备不周全的诱因
你可能会认为,所有的专业程序员都知道准备工作的重要性,并且在跃入构建活动之前会检查确认所有先决条件都已经满足了。很不幸,这不是事实。
造成准备工作不充分的一个常见原因是,那些分配去做前期准备活动的开发人员并不具备完成这一任务的专业技能。项目规划、创作引人注目的商业案例、分析出全面而准确的需求、创建高质量的架构等活动都需要一定的技能,这些技能不是轻而易举就能获得的。但是绝大多数开发人员都没有接受过针对这些活动的训练。当开发人员不知道如何进行这些前期工作的时候,建议“做更多的前期工作”就完全没有用:如果不能首先把这项工作做好,那么做再多也没有意义!说明如何进行这些活动已经超出了本书的范围,不过在本章最后的“更多资源”中,提供了许多获取这些专业技能的途径。
有一些程序员确实知道如何进行前期工作,但是他们并没有做,因为他们不能够抵抗“尽快开始编码”的欲望。如果你也是这样,我有两条建议:第一,
阅读下一节中的争论,它也许能告诉你一些你以前没有想到的问题;第二,注意一下你经历过的问题。只需要做几个大项目,你就能够体会到:事先做好计划能避免很多压力。让你自己的经验来引导你吧。
程序员不做准备工作的最后一个原因是,管理者们对那些“花时间进行构建活动的前期准备的程序员”的冷漠已经到了人神共愤的程度。Barry Boehm、Grady Booch及Karl Wiegers等人25年来一直在擂响需求和设计的战鼓,因此你可以期望,管理者们应该已经开始明白:软件开发不仅仅是写代码。
然而,就在几年前,我参与美国国防部的一个项目。当这个项目正集中精力做需求分析的时候,负责这个项目的那位军方将领来视察。我们告诉他正在开发需求,主要包括和客户沟通、捕捉需求,以及勾勒出设计的轮廓。但是他坚持无论如何要看到代码。我们告诉他现在没有代码,但是他还是在一个100人的工作区里面走来走去,试图抓出一个正在编程的人来。看到这么多人要么不在桌子前,要么正在做需求分析和设计,他感到非常的沮丧。最后这个腆着大肚子的家伙指着坐在我旁边的一个工程师,扯着喉咙咆哮道:“他在干什么?他肯定在写代码!”事实上这个工程师正在做一个文档格式化的工具,但是这位将军想要找代码,觉得这个像是代码,并且希望这位工程师是在写代码,于是我们告诉他这就是代码。
这种现象被称为WISCA综合症或者WIMP综合症:Why Isn’t Sam Coding Anything?(为什么Sam不在写代码?)或者Why Isn’t Mary Programing?(为什么Mary不在编程?)
如果你的项目经理装成陆军准将的样子,命令你立刻开始写代码,你可以轻易地说:“遵命!”(这有什么可怕的?老手当然知道他在说什么。)这种回答很糟糕,你可以有几种更好的替代方案。首先,你可以断然拒绝以这种无效的命令,假如你和老板的关系不错,而且你银行账户的钱数也支持你这么做的话,祝你好运。
第二个不太靠得住的方案是假装在写代码,而事实上不是。在你的桌角上放一份旧的程序代码清单,然后投入需求和架构的开发中,同时不用去理会老板同不同意。你将能够更快地完成项目,并得到更高的质量。有些人会觉得这种方法不合乎伦理,但是从你老板的视角来看,无知是福。
第三种方法是,你可以教育你的老板,告诉他技术项目的微妙之处。这是一个好办法,因为它能增加世界上脱盲的老板的人数。下面的小节将继续讲述“在构建活动之前花时间做前期准备”的根本原因。
最后一个方案是,你可以另外找一份工作。虽然经济景气程度时高时低,但是优秀的程序员永远是紧缺的(BLS 2002)。人生苦短,当有大量更好的选择摆在你面前的时候,在一个荒蛮的软件企业中工作是不明智的。
Utterly Compelling and Foolproof Argument for Doing Prerequisites Before Construction
关于开始构建之前要做前期准备的绝对有力且简明的论据
设想你已经到过“问题定义”之山,与名为“需求”之人同行数里,在“架构”之泉面前脱下脏兮兮的外套,然后在“前期准备”之纯净水中沐浴。那么你就会知道,在实现一个系统之前,你需要理解“这个系统应该做什么”,以及“它该如何做到这些”。
作为技术雇员,你的一部分工作就是培训周围的非技术人员,讲解开发过程。本节将有助你应对那些“尚未觉悟的管理者和老板”。这里有支持“在开始编码、测试、调试之前进行需求分析和架构设计——才能保证关键的方面都做正确”这一观点的大量论据。学习这些论据,然后与老板一同坐下来,进行一次有关开发过程的恳谈。
Appeal to Logic
诉诸逻辑
进行有效编程的要领之一是:准备工作很重要。在开始做一个大项目之前,应该为这个项目制订计划,这是很有意义的。大的项目需要做更多的计划,而小项目则可以少些。从管理的角度看,做计划意味着确定项目所需要用的时间、人数以及计算机台数。从技术角度讲,做计划意味着弄清楚你想要建造的是什么,以防止浪费钱去建造错误的东西。有时候用户在一开始并不完全确定自己想要的是什么,因此值得花费比理想情况下更多的力气,找出他们真正想要的东西。但这至少比“先做一个错误的东西出来,然后扔掉,并从头来过”的成本要低廉。
在开始动手制作这个系统之前,先好好思考打算如何去做,这也非常重要。你总不希望花费很多的时间和金钱,却毫无必要地走进死胡同(尤其当这样做会增加成本的时候)。
Appeal to Analogy
诉诸类比
建造软件系统跟其他任何花费人力财力的项目是相似的。如果打算建造一座房屋,你需要在开始钉钉子之前准备好手绘草图(表达设计概念)和蓝图(即设计详图,包含所有细节信息)。在浇注混凝土之前必须审核蓝图并获得批准。在软件领域做技术规划也包含同样多的事情。
在把圣诞树立起来之前,你不会对它做装饰;在打开烟囱之前,你不会生火;你不会在车子的油箱是空的时候上路去长途旅行;你不会在洗完澡之前就穿戴整齐,也不会在穿袜子之前就穿鞋。在做软件时,你也必须按正确的顺序去做事情。
程序员是软件食物链的最后一环。架构师吃掉需求,设计师吃掉架构,而程序员则消化设计。
我们用真实的食物链来比喻软件食物链。在健康的生态环境中,海鸥吃新鲜的鲑鱼。这对海鸥是营养丰富的大餐,因为鲑鱼吃的是新鲜的青鱼,而青鱼吃的是新鲜的水蝽。这是一条健康的食物链。在软件开发中,如果食物链的每一级都有健康的食物,那么最终就会获得由快乐的程序员编写出的健康的代码。
在受到污染的环境中,水蝽在核废料中游泳,青鱼被聚氯联二苯(PCB)污染,而吃青鱼的鲑鱼又在泄漏的原油中游荡。3海鸥,很不幸,它位于食物链的最后一环,因此它吃下去的不仅仅是不健康的鲑鱼体内的原油,还有青鱼体内的聚氯联二苯和水蝽体内的核废料。在软件开发中,如果需求被污染了,那么它就会污染架构,而架构又会污染构建。这样会导致程序员脾气暴躁、营养失调;开发出的软件具有放射性污染,而且周身都是缺陷。
如果你正为某个高度迭代的项目做计划,那么在开始构建活动之前,你需要针对将要构造的每一片段,先弄清哪些是最关键的需求和架构要素。建造住宅小区的施工人员,在开始建造第一栋房子之前,并不需要知道小区里面每一栋房子的每一个细节。但他会调查施工场所,制定下水道和电线的走向等。如果施工人员准备不充分,那么建造过程很可能会因为“需要在某所已经造好的房子的地下挖一条下水道”而延误。
过去25年来的研究确凿地证明了,在一开始就把事情做好是最合算的。进行非必要的改动的代价是高昂的。
惠普、IBM、休斯飞机公司、TRW以及其他组织的研究人员发现,在构建活动开始之前清除一个错误,那么返工的成本仅仅是“在开发过程的最后阶段(在系统测试期间或者发布之后)做同样事情”的十分之一到百分之一。(Fagan 1976; Humphrey, Snyder, and Willis 1991; Leffingwell 1997; Willis et al. 1998; Grady 1999; Shull et al. 2002; Boehm and Turner 2004。)
一般而言,这里的原则是:发现错误的时间要尽可能接近引入该错误的时间。缺陷在软件食物链里面呆的时间越长,它对食物链的后级造成损害就越严重。由于需求是首先要完成的事情,需求的缺陷就有可能在系统中潜伏更长的时间,代价也更加昂贵。在软件开发过程的上游引入的缺陷通常比那些在下游引入的缺陷具有更广泛的影响力。这也使得早期的缺陷代价更加高昂。
表3-1展示了“引入缺陷的时间和找到缺陷的时间”与“修复缺陷的费用”之间的关系。
表3-1 修复缺陷的平均成本与引入缺陷的时间和检测到该缺陷的时间之间的关系
|
检测到缺陷的时间 |
|
引入缺陷的时间 需求 架构 构建 系统测试 发布之后 需求 1 3 5—10 10 10—100 架构 — 1 10 15 25—100 构建 — — 1 10 10—25 |
|
来源:改写自“Design and Code Inspections to Reduce Errors in Program Development” (Fagan 1976), 《Software Defect Removal 》 (Dunn 1984), “Software Process Improvement at Hughes Aircraft” (Humphrey, Snyder, and Willis 1991), “Calculating the Return on Investment from More Effective Requirements Management” (Leffingwell 1997), “Hughes Aircraft’s Widespread Deployment of a Continuously Improving Software Process” (Willis et al. 1998), “An Economic Release Decision Model: Insights into Software Project Management” (Grady 1999), “What We Have Learned About Fighting Defects” (Shull et al. 2002), and 《Balancing Agility and Discipline: A Guide for the Perplexed》 (Boehm and Turner 2004). |
表3-1的数据显示,例如,假设在创建架构的期间修复某个架构缺陷需要花1 000美元,那么在系统测试期间修复这一缺陷,将要花费15 000美元。图3-1解释了同样的现象。

图3-1 修复缺陷的成本随着“从引入缺陷到检测该缺陷之间的时间”变长而急剧增加。无论项目是高度序列化(sequential)的(预先完成100%的需求和设计),还是高度迭代型(预先完成5%的需求和设计)的,这些都成立
平均水平的项目仍然把绝大部分的缺陷修正工作放到图3-1的右侧进行,这也就意味着“调试连同相应的返工”在典型的软件开发周期中会占据大约50%的时间。(Mills 1983; Boehm 1987a; Cooper and Mullen 1993; Fishman 1996; Haley 1996; Wheeler, Brykczynski, and Meeson 1996; Jones 1998; Shull et al. 2002; Wiegers 2002。) 许多公司发现,只需在项目中尽早集中纠正缺陷,就能将开发的成本和时间减半(甚至更多)(McConnell 2004)。所以你应该尽早查找并修正错误。
如果你觉得你的老板已经明白了“在开始构建之前进行前期准备”的重要性,那么试试以下的测试,以确保他确实明白了。
下面的句子哪些是自我实现的预言(self-fulfilling prophecies)4?
■ 我们最好立刻开始编码,因为将会有很多的调试工作需要做。
■ 我们并没有为测试安排太多的时间,因为将来不会发现多少缺陷。
■ 我们已经非常详细地研究了需求和设计,我想不出在编码和调试期间还会遇到什么大问题。
上面这些陈述都是自我实现的预言。要瞄准最后那个。
如果你仍然不能确信前期准备适用于你的项目,下面一节将帮助你做出决定。







