- 159 ------------------------ Page 172-----------------------作,所以他可以尝试任何感到不确定的快捷键。另外,他可以检查菜单,以确定命令是否有效。新手会大量地使用菜单,而熟练用户几乎不使用,中间用户仅偶尔需要访问菜单,因为每个人都了解组成自己大多数操作的少数快捷键。我们大多数的开发设计人员对这样的界面非常熟悉,对其优雅而强大的功能感到非常欣慰。强制体系结构的实施,作为设备的直接整合。Mac 界面在另一个方面很值得注意。没有任何强迫,它的设计人员在所有的应用程序中使用标准界面,包括了大量的第三方程序。从而,用户在界面上获得的概念一致性不仅仅局限在机器所配备的软件,而且遍及所有的应用程序。Mac 设计人员把界面固化到只读内存中,从而开发者使用这些界面比开发自己的特殊界面更容易和快速。这些获取一致性的措施得到了足够广泛的应用,以致于可以形成实际的标准。Apple 的管理投入和大量说服工作协助了这些措施。产品杂志中很多独立评论家,认识到了跨应用概念完整性的巨大价值,通过批评不遵从产品的反面例子,对上述方法进行了补充。这是第6 章中所推荐技术的一个非常杰出的例子,该技术通过鼓励其他人直接将某人的代码合并到自己的产品中来获得一致性,而不是试图根据某人的技术说明开发自己的软件。WIMP 的命运:过时被淘汰。尽管WIMP 有很多优点,我仍期望WIMP 界面在下一代技术中成为历史。如同我们支配我们机器一样,指针选取仍将是表达名词的方式,语音则无疑成为表达动词的方法。Mac 上的Voice Navigator 和PC 上的Dragon 已经提供了这种能力。没有构建舍弃原型——瀑布模型是错误的!一幅让人无法忘怀的图画,倒塌的塔科马大桥,开启了第11章。文中强烈地建议:“为舍弃而计划。无论如何,你一定要这样做。”现在我觉得这是错误的,并不是因为它太过极端,而是因为它太过简单。在《未雨绸缪》一章体现的概念中,最大的错误是它隐含地假设了使用传统的顺序或者瀑布开发模型。该模型源自于类似甘特图布局的阶段化流程,常常绘制成图19.1 的形状。Winton Royce 在1970年的一篇经典论文中,改进了顺序模型,他提出:- 160 ------------------------ Page 173-----------------------存在一些从一个阶段到前一个阶段的反馈将反馈限定在直接相邻的先前阶段,从而容纳它引起的成本增加和进度延迟计划编码单元测试系统测试现场支持图19.1:软件开发的瀑布模型他给开发者提出的建议“构建两次”比《人月神话》的好8。受到瀑布模型不良影响并不只是第11章,而是从第2 章的进度计划规则开始,贯穿了整本书。第2 章中的经验法则分配了1/3 的时间用于计划,1/6用于编码,1/4用于单元测试以及1/4用于系统测试。瀑布模型的基本谬误是它假设项目只经历一次过程,而且体系结构出色并易于使用,设计是合理可靠的,随着测试的进行,编码实现是可以修改和调整的。换句话说,瀑布模型假设所有错误发生在编码实现阶段,因此它们的修复可以很顺畅地穿插在单元和系统测试中。实际上,《未雨绸缪》并没有迎面痛击这个错误。它不是对错误的诊断,而是补救措施。现在,我建议应该一块块地丢弃和重新设计系统,而不是一次性地完成替换。就目前的情况而论,这没有问题,但它并没有触及问题的根本。瀑布模型把系统测试,以及潜在地把用户测试放在构件过程的末尾。因此,只有在投入了全部开发投资之后,才能发现无法接受的性能问题、笨拙功能以及察觉用户的错误或不当企图。不错,Alpha 测试对规格说明的详细检查是为了尽早地发现这些缺陷,但是对于实际参与的用户却没有对应的措施。瀑布模型的第二个谬误是它假设整个系统一次性地被构建,在所有的设计、大部分编码、部分单元测试完成之后,才为闭环的系统测试合并各个部分。瀑布模型,这个大多数人在1975 年考虑的软件项目开发方法,不幸地被奉为军用标准DOD-STD-2167,作为所有国防部军用软件的规范。所以,在大多数有见地的从业者认识到瀑- 161 ------------------------ Page 174-----------------------9布模型的不完备并放弃之后,它仍然得以幸存。幸运的是,DoD 也已经慢慢察觉到这一点 。必须存在逆向移动。就像本章开始图片中精力充沛的大马哈鱼一样,在开发过程“下游”的经验和想法必须跃行而上,有时会超过一个阶段,来影响“上游”的活动。例如,设计实现会发觉有些体系结构的功能定义会削弱性能,从而体系结构必须重新调整。编码实现会发现一些功能会使空间剧增,超过要求,因此必须更改体系结构定义和设计实现。所以,在把任何东西实现成代码之前,可能要往复迭代两个或更多的体系结构-设计-实现循环。增量开发模型更佳——渐进地精化构建闭环的框架系统从事实时系统开发的Harlan Mills,早期曾提倡我们首先应该构建实时系统的基本轮询回路,为每个功能都提供子函数调用(占位符),但仅仅是空的子函数(图 19.2)。对它10进行编译、测试,使它可以不断运行。它不直接完成任何事情,但至少是正常运行的 。注:MAIN LOOP-主循环Subroutines-子函数图19.2接着,我们添加(可能是基本的)输入模块和输出模块。瞧,一个可运行的系统出现了,尽管只是一个框架。然后,一个功能接一个功能,我们逐渐开发和增加相应模块。在每个阶段,我们都拥有一个可运行的系统。如果我们非常勤勉,那么每个阶段都会有一个经过- 162 ------------------------ Page 175-----------------------调试和测试的系统。(随着系统的增长,使用所有先前的测试用例对每个新模块进行的回归测试也采用这种方式进行。)在每个功能基本可以运行之后,我们一个接一个地精化或者重写每个模块——增量地开发(growing)整个系统。不过,我们有时的确需要修改原有的驱动回路,或者甚至是回路的模块接口。因为我们在所有时刻都拥有一个可运行的系统,所以我们可以很早开始用户测试,以及我们可以采用按预算开发的策略,来彻底保证不会出现进度或者预算的超支(以允许的功能牺牲作为代价)。我曾在北卡罗来纳大学教授软件工程实验课程22 年,有时与David Parnas 一起。在这门课程中,通常4 名学生的团队会在一个学期内开发某个真正的实时软件应用系统。大约是一半的时候,我转而教授增量开发的课程。我常常会被屏幕上第一幅图案、第一个可运行的系统对团队士气产生的鼓舞效果而感到震惊。Parnas 产品族在这整个20年的时间里,David Parnas 曾是软件工程思潮的带头人。每个人对他的信息隐藏概念都很熟悉,但对他另一个非常重要的概念——将软件作为一系列相关的产品族来设计11——相对了解较少。Parnas 力劝设计人员对产品的后期扩展和后续版本进行预测,定义它们在功能或者平台上的差异,从而搭建一棵相关产品的家族树(图19.3)。设计类似一棵树的技巧是将那些变化可能性较小的设计决策放置在树的根部。这样的设计使得模块的重用最大化。更重要的是,可以延伸相同的策略,使它不但可以包括发布产品,而且还包括以增量开发策略创建的后续中间版本。这样,产品可以通过它的中间阶段,以最低限度的回溯代价增长。- 163 ------------------------ Page 176-----------------------基产品内部版本功能修改 第二代产品 移植到其他平台图19.3Microsoft 的“每晚重建”方法JamsMcCarthy 向我描述了他的队伍和微软其他团队所使用的产品开发流程,这实际上是一种逻辑上的增量式开发。他说,在我们第一次产品发布之后,我们会继续发布后续版本,向已有的可运行系统添加更多的功能。为什么最初的构建过程要不一样呢?因此,从我们第一个里程碑开始[第一次发布有三个里程碑],我们每晚重建开发中的系统[以及运行测试用例]。该构建周期成了项目的“心跳”。每天,一个和多个程序员-测试员队伍提交若干具有新功能的模块。在每次重建之后,我们会获得一个可运行的系统。如果重建失败,我们将停下整个过程,直到找到问题所在并进行修复。在任何时间,团队中的每个人都了解项目的状态。这是非常困难的。你必须投入大量的资源,而且它是一个规范化、可跟踪、开诚布公的流程。它向团队提供了自身的可信度,而可信度决定了你的士气和情绪状态。其他组织的软件开发人员对这个过程感到惊讶,甚至震惊。其中一个人说:“我们可以实现每周一次的重建,但是如果每晚一次的话,我想不大可能,工作量太大了。”这可能是对的。例如,Bell 北方研究所就是每周重建1千2 百万行的系统。- 164 ------------------------ Page 177-----------------------增量式开发和快速原型增量开发过程能使真正的用户较早地参与测试,那么它与快速原型之间的区别是什么呢?我认为它们既是互相关联,又是相互独立的。各自可以不依赖对方而存在。Harel 将原型精彩地定义成:仅仅反映了概念模型准备过程中所做的设计决策[的一个程序版本],它并未反映受实12现考虑所驱使的设计决策 。构建一个完全不属于发布产品的原型是完全可能的。例如,可以开发一个界面原型,但是并不包含任何的实际功能,而仅仅是一个看上去履行了各个步骤的有限状态机。甚至可以通过模拟系统响应的向导技术来原型化和测试界面。这种原型化对获取早期的用户反馈非常有用,但是它和产品发布前的测试区别很大。类似的,实现人员可能会着手开发产品的某一块,并完整地实现该部分的有限功能集合,从而可以尽早发现性能上的潜在问题。那么,“从第一个里程碑开始构建”的微软流程和快速原型之间的差别是什么?功能。第一个里程碑产品可能不包含足够的功能使任何人对它产生兴趣,而可发布产品和定义中的一样,在完整性上——配备了一系列实用的功能集,在质量上——它可以健壮地运行。关于信息隐藏,Parnas 是正确的,我是错误的在第7 章中,关于每个团队成员应该在多大程度上被允许和鼓励相互了解设计和代码的问题,我对比了两种方法。在操作系统OS/360 项目中,我们决定所有的程序员应该了解所有的材料——每个项目成员都拥有一份大约10,000页的项目工作手册拷贝。HarlanMills颇有说服力地指出“编程是个开放性的公共过程”。把所有工作都暴露在每个人的凝视之下,能够帮助质量控制,这既源于其他人优秀工作的压力,也由于同伴能直接发现缺陷和bug。这个观点和David Parnas 的观点形成了鲜明的对比。David Parnas认为代码模块应该采用定义良好的接口来封装,这些模块的内部结构应该是程序员的私有财产,外部是不可见13的。编程人员被屏蔽而不是暴露在他人模块内部结构面前。这种情况下,工作效率最高 。我在第7 章中并不认同Parnas 的概念是“灾难的处方”。但是,Parnas 是正确的,我是错误的。现在,我确信信息隐藏——现在常常内建于面向对象的编程中——是唯一提高软- 165 ------------------------ Page 178-----------------------件设计水平的途径。实际上,任何技术的使用都可能演变成灾难。Mill 的技术是通过了解接口另一侧的情况,使编程人员能理解他们所工作接口的详细语义。这些接口的隐藏会导致系统的bug。Parnas 的技术在面对变更时是很健壮的,更加适合为变更设计的理念。第16章指出了下列情况:过去在软件生产率上取得的进展大多数来自消除非内在的困难,如笨拙的编程语言、漫长的批处理周转时间等。像这些比较容易解决的困难已经不多了。彻底的进展将来自对根本困难的处理——打造和组装复杂概念性结构要素。最明显的实现这些的方法是,认为程序由比独立的高级语言语句、函数、模块或类等更大的概念结构要素组成。如果能对设计和开发进行限制,我们仅仅需要从已建成的集合中参数化这些结构要素,并把它们组装在一起,那么我们就能大幅度提高概念的级别,消除很多无谓的工作和大量语句级别的错误可能性。Parnas 的模块信息隐藏定义是研究项目中的第一步,它是面向对象编程的鼻祖。Parnas把模块定义成拥有自身数据模型和自身操作集的软件实体。它的数据仅仅能通过它自己的操作来访问。第二步是若干思想家的贡献:把Parnas 模块提升到抽象数据类型,从中可以派生出很多对象。抽象数据类型提供了一种思考和指明模块接口的统一方式,以及容易保证实施的类型规范化访问方法。第三步,面向对象编程引入了一个强有力的概念——继承,即类(数据)默认获得类继承层次中祖先的属性14。我们希望从面向对象编程中得到的最大收获实际上来自第一步,模块隐藏,以及预先建成的、为了重用而设计和测试的模块或者类库。很多人忽视了这样一个事实,即上述模块不仅仅是程序,某种意义上是我们在第1章中曾讨论过的编程产品。许多人希望大规模重用,但不付出构建产品级质量(通用、健壮、经过测试和文档化的)模块所需要的初始代价——这种期望是徒劳的。面向对象编程和重用在第16和17章中有所讨论。- 166 ------------------------ Page 179-----------------------人月到底有多少神话色彩?Boehm 的模型和数据很多年来,人们对软件生产率和影响它的因素进行了大量的量化研究,特别是在项目人员配备和进度之间的平衡方面。最充分的一项研究是Barry Boehm 对63 个项目的调查,其中大多数是航空项目和25个TRW 公司的项目。他的《软件工程经济学》(Software Engineering Economics)不但包括了很多结果,而且还有一系列逐步推广的成本模型。尽管一般商业软件的成本模型和根据政府标准开发的航空软件成本模型中的系数肯定不同,不过他的模型使用了大量的数据来支撑。我想从现在起,这本书将作为一代经典。他的结果充分地吻合了《人月神话》的结论,即人力(人)和时间(月)之间的平衡远不是线性关系,使用人月作为生产率的衡量标准实际是一个神话。特别的,他发现:151/3第一次发布的成本最优进度时间,T = 2.5 (MM) 。即,月单位的最优时间是估计工作量(人月)的立方根,估计工作量则由规模估计和模型中的其他因子导出。最优人员配备曲线是由推导得出的。当计划进度比最优进度长时,成本曲线会缓慢攀升。时间越充裕,花的时间也越长。当计划进度比最优进度短时,成本曲线急剧升高。无论安排多少人手,几乎没有任何项目能够在少于3/4 的最优时间内获得成功!当高级经理向项目经理要求不可能的进度担保时,这段结论可以充分地作为项目经理的理论依据。Brooks 准则有多准确?曾有很多细致的研究来评估Brooks法则的正确性,简言之,向进度落后的软件项目中添加人手只会使进度更加落后。最棒的研究发表在Abdel-Hamid 和