15.1 对于软件编程产品来说,程序向用户所呈现的面貌与提供给机器识别的内容同样重要。
15.2 即使对于完全开发给自己使用的程序,描述性文字也是必须的,因为它们会被用户-作者所遗忘。
15.3 培训和管理人员基本上没有能向编程人员成功地灌输对待文档的积极态度—
—文档能在整个生命周期对克服懒惰和进度的压力起促进激励作用。
15.4 这样的失败并不都是因为缺乏热情或者说服力,而是没能正确地展示如何有效和经济地编制文档。
15.5 大多数文档只提供了很少的总结性内容。必须放慢脚步,稳妥地进行。
15.6 由于关键的用户文档包含了跟软件相关的基本决策,所以它的绝大部分需要在程序编制之前书写,它包括了9项内容(参见相应章节)。
15.7 每一份发布的程序拷贝应该包括一些测试用例,其中一部分用于校验输入数据,一部分用于边界输入数据,另一部分用于无效的输入数据。
15.8 对于必须修改程序的人而言,他们所需要程序内部结构文档,同样要求一份清晰明了的概述,它包括了5项内容(参见相应章节)。
15.9 流程图是被吹捧得最过分的一种程序文档。详细逐一记录的流程图是一件令人生厌的事情,而且高级语言的出现使它显得陈旧过时。(流程图是图形化的高级语言。)
15.10 如果这样,很少有程序需要一页纸以上的流程图。[在这一点上,MILSPEC军用标准实在错得很厉害。]
15.11 即使的确需要一张程序结构图,也并不需要遵照ANSI的流程图标准。
15.12 为了使文档易于维护,将它们合并至源程序是至关重要的,而不是作为独立文档进行保存。
15.13 最小化文档负担的3个关键思路:
借助那些必须存在的语句,如名称和声明等,来附加尽可能多的“文档”信息。
使用空格和格式来表现从属和嵌套关系,提高程序的可读性。
以段落注释,特别是模块标题的形式,向程序中插入必要的记叙性文字。
15.14 程序修改人员所使用的文档中,除了描述事情如何以外,还应阐述它为什么那样。对于加深理解,目的是非常关键的,但即使是高级语言的语法,也不能表达目的。
15.15 在线系统的高级语言(应该使用的工具)中,自文档化技术发现了它的绝佳应用和强大功能。
原著结束语
E.1 软件系统可能是人类创造中最错综复杂的事物(从不同类型组成部分数量的角度出发)。
E.2 软件工程的焦油坑在将来很长一段时间内会继续地使人们举步维艰,无法自拔。
20年后的人月神话(The Mythical Man-Month after 20 Years)
只能根据过去判断将来。
- 帕特里克·亨利
然而永远无法根据过去规划将来。
- 埃德蒙·伯克
I know no way of judging the future but by the past.
- PATRICK HENRY
You can never plan the future by the past.
- EDMUND BURKE
为什么会出现二十周年纪念版本?
飞机划破夜空,嗡嗡地飞向纽约的拉瓜迪亚机场。所有的景色都隐藏在云层和黑暗之中。我正在看一篇平淡无奇的文档,不过并没有感到厌烦。紧挨着我的一位陌生人正在阅读《人月神话》,我在旁边一直等待着,看他是否会通过文字或者手势做出反映。最后,当我们向舱门移动时,我无法再等下去了:
“这本书如何?你有什么评论吗?”
“噢!这里面的东西我早就知道。”
此刻,我决定不介绍自己。
为什么《人月神话》得以持续?为什么看上去它仍然和现在的软件实践相关?为什么
它还拥有软件工程领域以外的读者群,律师、医生、社会学家、心理学家,和软件人员一样,不断地对这本书提出评论意见,引用它,并和我保持通信?20年前的一本关于30年前软件开发经验的书,如何能够依然和现实情况相关?更不用说有所帮助了。
常听到的一个解释是软件开发学科没有正确地发展,人们经常通过比较计算机软件开发生产率和硬件制造生产率来支持这个观点,后者在20年内至少翻了1000倍。正像第16章所解释的,反常的并不是软件发展得太慢,而是计算机硬件技术以一种与人类历史不相配的方式爆发出来。大体上这源于计算机制造从装配工业向流水线工业、从劳动密集型向资金密集型的逐渐过渡。与生产制造相比,硬件和软件开发保持着固有的劳动密集型特性。
第二个经常提及的解释——《人月神话》仅仅是顺便提及了软件,而主要针对团队中的成员如何创建事物。这种说法的确有些道理,1975年版本的前言中提到,软件项目管理并不像大多数程序员起初所认为的那样,而更加类似于其他类型的管理。现在,我依然认为这是正确的。人类历史是一个舞台,总是上演着相同的故事。随着文化的发展,这些故事的剧本变化非常缓慢,而舞台的布局却在随时改变。正是如此,我们发现二十世纪本身会反映在莎士比亚、荷马的作品和圣经中。因此,某种程度上,《人月神话》是关于人与团队的书,所以它的淘汰过程会是缓慢的。
不管出于什么原因,读者仍然在购买这本书,并且常给我发一些致谢的评论。现在,我常常被问到:“你现在认为哪些在当时就是错误的?哪些是现在才过时的?哪些是软件工程领域中新近出现的?”这些独特的问题是完全平等的,我将尽我最大的能力来回答它们。不过,不以上述顺序,而是按照一系列主题来答复。首先,让我们考虑那些在写作时就正确,现在依然成立的部分。
核心观点:概念完整性和结构师
概念完整性。一个整洁、优雅的编程产品必须向它的每个用户提供一个条理分明的概念模型,这个模型描述了应用、实现应用的方法以及用来指明操作和各种参数的用户界面使用策略,。用户所感受到的产品概念完整性是易用性中最重要的因素。(当然还有其他因素。Macintosh上所有应用程序界面的统一就是一个重要的例子。此外,有可能建立统一的接口,尽管它可能很粗糙,就像MS-DOS。)
有很多由一个或者两个人设计的优秀软件产品例子。大多数纯智力作品,像书籍、音
乐等都是采用这种方式创作出来的。不过,很多产业的产品开发过程无法负担这种获取概念完整性的直接方法。竞争带来了压力,很多现代工艺的最终产品是非常复杂的,它们的设计需要很多人月的工作量。软件产品十分复杂,在进度上的竞争也异常激烈。
任何规模很大或者非常紧急,并需要很多人力的项目,都会碰到一个特别的困难:必须由很多人来设计,但与此同时,还需要在概念上保持与单个使用人员的一致。如何组织设计队伍来获得上述的概念一致性?这是《人月神话》关注的主要问题。其中一点:由于参与人数的不同,大型编程项目的管理与小型项目在性质上都不同。为了获得一致性,经过深思熟虑的,有时甚至是英勇的管理活动是完全必要的。
结构师。从第4到第7章,我一直不断地在表达一个观点——委派一名产品结构师是最重要的行动。结构师负责产品所有方面的概念完整性,这些是用户能实际感受到的。结构师开发用于向用户解释使用的产品概念模型,概念模型包括所有功能的详细说明以及调用和控制的方法。结构师是这些模型的所有者,同时也是用户的代理。在不可避免地对功能、性能、规模、成本和进度进行平衡时,卓有成效地体现用户的利益。这个角色是全职工作,只有在最小的团队中,才能和团队经理的角色合并。结构师就像电影的导演,而经理类似于制片人。
将体系结构和设计实现、物理实现相分离。为了使结构师的关键任务更加可行,有必要将用户所感知的产品定义——体系结构,与它的实现相分离。体系结构和实现的划分在各个设计任务中形成了清晰的边界,边界两边都有大量的工作。
体系结构的递归。对于大型系统,即使所有实现方面的内容都被分离出去,一个人也无法完成所有的体系结构工作。所以,有必要由一位主结构师把系统分解成子系统,系统边界应该划分在使子系统间接口最小化和最容易严格定义的地方。每个部分拥有自己的结构师,他必须就体系结构向主结构师汇报。显然,这个过程可以根据需要重复递归地进行。
今天,我比以往更加确信。概念完整性是产品质量的核心。拥有一位结构师是迈向概念完整性的最重要一步。这个原理决不仅限于软件系统,它适用于所有的复杂事物,如计算机、飞机、防御系统、全球定位系统等。在软件工程试验室进行20次以上的讲授之后,我开始坚持每4个学生左右的小组就选择不同的经理和结构师。在如此小的队伍中定义截然不同的角色可能是有点极端,但我仍然发现这种方法即使对小型团队也运作良好,并且促使了设计的成功。
开发第二个系统所引起的后果:盲目的功能和频率猜测
为大型用户群设计。个人计算机革命的一个结果是,至少在商业数据处理领域中,客户应用程序越来越多地被商用软件包所代替。而且,标准软件包以成百上千,甚至是数百万拷贝的规模出售。源厂商支持性软件的系统结构师必须不断地为大型的不确定用户群,而不是为某个公司的单一、可定义的应用进行设计。许许多多的系统结构师现在面临着这项任务。
但自相矛盾的是,设计通用工具比设计专用工具更加困难,这是因为必须为不同用户的各种需要分配权重。
盲目的功能(Featuritis)。对于如电子表格或字处理等通用工具的结构师,一个不断困扰他们的诱惑是以性能甚至是可用性的代价,过多地向产品添加边界实用功能。
功能建议的吸引力在初期阶段是很明显的,性能代价在系统测试时才会出现。而随着功能一点一点地添加,手册慢慢地增厚,易用性损失以不易察觉的方式蔓延。1
对幸存和发展了若干代的大众软件产品,这种诱惑特别强烈。数百万的用户需要成百上千的功能特色,任何需求本身就是一种“市场需要它”的证明。而常见的情况是,原有的系统结构师得到了嘉奖,正工作在其他岗位或项目上,现在负责体系结构的结构师,在均衡表达用户的整体利益方面,往往缺乏经验。一个对Microsoft Word 6.0的近期评价声称“Word 6.0对功能特性进行了打包,通过包缓慢地更新 Word 6.0同样是大型和慢速的。”有点令人沮丧的是——Word 6.0需要4MB内存,丰富的新增功能意味着“甚至Macintosh IIfx都不能胜任Word 6的任务”2。
定义用户群。用户群越大和越不确定,就越有必要明确地定义用户群,以获得概念完整性。设计队伍中的每个成员对用户都有一幅假想的图像,并且每个设计者的图像都是不同的。结构师的用户图像会有意或者无意地影响每个结构决策,因此有必要使设计队伍共享一幅相同的用户图像。这需要把用户群的属性记录下来,包括:
他们是谁
他们需要(need)什么
他们认为自己需要(need)什么
他们想要(want)的是什么
频率。对于软件产品,任何用户群属性实际上都是一种概率分布,每个属性具有若干可能的值,每个值有自己的发生频率。结构师如何成功地得到这些发生频率?对并未清晰定义的对象进行调查是一种不确定和成本高昂的做法3。经过很多年,我现在确信,为了得到完整、明确和共有的用户群描述,结构师应该猜测(guess),或者假设(postulate)完整的一系列属性和频率值。
这种不是很可靠的过程有很多好处。首先,仔细猜测频率的过程会使结构师非常细致地考虑对象用户群。其次,把它们写下来一般会引发讨论,这能起到解释的作用,以及澄清不同设计人员对用户图像认识上的差异。另外,明确地列举频率能帮助大家认识到哪些决策依赖于哪些用户群属性。这种非正式的敏感性分析也是颇有价值的。当某些非常重要的决策需要取决于一些特殊的猜测时,很值得为那些数值花费精力来取得更好的估计。(Jeff Conklin开发的gIBIS提供了一种工具,能精确和正式地跟踪设计决策和文档化每个决策的原因4。我还没有机会使用它,但是我认为它应该非常有帮助。)
总结:为用户群的属性明确地记载各种猜测。清晰和错误都比模糊不清好得多。
“开发第二系统所引起的后果(second-system effect)”情况怎样?一位敏锐的学生说,人月神话推荐了一剂对付灾难的处方:计划发布任何新系统的第二个版本(第11章),第二系统在第5章中被认为是最危险的系统。
这实际上是语言引起的的差异,现实情况并不是如此。第5章中提到的“第二个”系统是第二个实际系统,它是引入了很多新增功能和修饰的后续系统。第11章中的“第二个”系统指开发第一个实际系统所进行的第二次尝试。它在所有的进度、人员和范围约束下开发,这些约束刻画了项目的特征,形成了开发准则的一部分。
图形(WIMP)界面的成功
在过去20年内,软件开发领域中,令人印象最深刻的进步是窗口(Windows)、图标(Icons)、菜单(Menus)、指针选取(Pointing)界面的成功——或者简称为WIMP。这些在今天是如此的熟悉,以致于不需要任何解释。这个概念首先在1968年西部联合计算机大会(Western Joint Computer Converence)上,由斯坦福研究机构(Stanford Research Institute)的Doug Englebart公开提出5。接着,这种思想被Xerox Palo Alto Research Center所采纳,用在了由Bob Taylor和他的团队所开发的Alto个人工作站中。Steve Jobs
在Apple Lisa型计算机中应用了该理念,不过Apple Lisa运行速度太慢,以致于无法承载这个令人激动的易用性概念。后来在1985年,Jobs在取得商业成功的Apple Macintosh机器上体现了这些想法。接下来,它们被IBM PC及其兼容机的Microsoft Windows所采用。我自己的例子则是Mac版本6。
通过类比获得的概念完整性。WIMP是一个充分体现了概念完整性的用户界面例子,完整性的获得是通过采用大家非常熟悉的概念模型——对桌面的比喻,以及一致、细致的扩展,后者充分发挥了计算机的图形化实现能力。例如,窗口采用覆盖,而不是排列的方式,这直接来自类比。尽管这种方法成本很高,但却是正确的决定。计算机图形介质提供了对窗口尺寸的调整,这是一种保持一致概念的延伸,给用户提供了新的处理能力,桌面上的文件是无法轻易地调整大小和改变形状的;拖放功能则直接出自模仿,使用指针来选择图标是对人用手拾起东西的直接模拟;图标和嵌套文件夹源于桌面的文档,回收站也是如此;剪切、复制和粘贴则完全反映了我们使用文档的一些习惯;我们甚至可以通过向回收站拖拽磁盘的图标来弹出磁盘——象征手法是如此的贴切,扩展是如此的连贯一致,以致于新用户常常会被它所体现出的理念打动。
哪些地方使WIMP界面远远超越了桌面的比喻?主要是在两个方面:菜单和单手操作。在真正的桌面上工作时,人们实际上是操作文档,而不是叫某人来完成这些动作。当要求他人进行某个活动时,常常是新产生,而不是选择一个口头或者书面祈使句:“请将这个归档。”“请找出前面一致的地方。”“请把这个交给Mary去处理。”
无论是手写还是口头的命令,现有处理能力还无法对自由产生的命令形式进行可靠的翻译和解释。所以,界面设计人员从用户对文档的直接动作中去除了上面提到的两个步骤。他们非常聪明地从桌面文档操作中选取了一些常用命令,形成了类似于公文的“便条”,用户只需从一些语义标准的强制命令菜单中进行选择。这个概念接着被延伸到水平菜单和垂直的下拉子菜单中。
命令表达和双光标问题。命令是祈使句,它们通常都有一个动词和直接宾语。对于任何动作,必须指定一个动词和一个名词。对事物选取的直接模仿要求:使用屏幕上不同的两个光标,同时指定两件事物。每个光标分别由左右手中的鼠标来控制。毕竟,在实际的桌面上,我们通常使用两只手来操作。(不过,一只手常常是将东西固定在某处,这一点在计算机桌面是默认情况。)而且,我们当然具备双手操作的能力,我们习惯上使用双手来打字、
驾驶、烹饪。但是,提供一个鼠标已经是个人计算机制造商向前迈进的一大步,没有任何商业系统可以容纳由双手分别控制的两只鼠标同时进行的动作7。
界面设计人员接受了现实,为一只鼠标设计。设计人员采用的句法习惯是首先指出(选择)名词的,接着指出一个动词菜单项。这确实牺牲了很多易用性。当我看到用户、或者用户录像、或者光标移动的计算机跟踪情况。我立刻对一个光标必须完成两件事而感到惊讶:选择窗口上桌面部分的一个对象,再选择菜单部分的一个动词,寻找或者重新寻找桌面上的一个对象。接着,拉下菜单(常常是同一个)选择一个动词。光标来来往往、周而复始地从数据区移到菜单区,每一次都丢弃了一些有用的位置信息“上次在这个空间的什么地方”——总而言之,是一个低效的过程。
一个卓越的解决方案。即使软件和器材可以很容易实现两个同时活动的光标,也仍然存在一些空间布局上的困难。WIMP象征手法中的桌面实际上包括了一个打字机,它必须在实际桌面的物理空间中容纳一个物理键盘。键盘加上两个鼠标垫会占据大量双手所及的空间。不过,键盘问题实际上是一个机会——为什么不一只手在键盘上指定动词,另一只手使用鼠标来指定名词,从而使高效的双手操作成为可能?这时,光标停留在数据区,为后续点击拾取提供了充分的空间活动能力。真正的高效,真正强大的用户功能。
用户功能和易用性。不过,这个解决方案舍弃了一些易用性——菜单提供了任何特定状态下的一些可选的有效动词。例如,我们可以购买某个商品,将它带回家,紧记购买的目的,遵照菜单上不同的动词略为试验一下,就可以开始使用,并不需要去查看手册。
软件结构师所面临的最困难问题是如何确切地平衡用户功能和易用性。是为初学者或偶尔使用的用户设计简单操作,还是为专业用户设计强大的功能?理想的答案是通过概念一致的方式把两者都提供给用户——这正是WIMP界面所达到的目标。每个频繁使用的菜单动词(命令)都有一个快捷键,因此可以作为组合通过左手一次性地输入。例如,在Mac机器上,命令键(♂[jypan2])正好在Z和X键的下方,因此使用最频繁的操作被编码成♂z、♂x、♂c、♂v、♂s。
从新手向熟练用户的逐渐过渡。双重指定命令动词的系统不但满足了新手快速学习的需要,而且它在不同的使用模式之间提供了平滑的过渡。被称为快捷键的字符编码,显示在菜单上的动词旁边,因此拿不准的用户可以激活下拉菜单,检查对应的快捷键,而不是直接在菜单上选取。每个新手从他最常使用的命令中学习快捷键。由于♂z可以撤消任何单一操
作,所以他可以尝试任何感到不确定的快捷键。另外,他可以检查菜单,以确定命令是否有效。新手会大量地使用菜单,而熟练用户几乎不使用,中间用户仅偶尔需要访问菜单,因为每个人都了解组成自己大多数操作的少数快捷键。我们大多数的开发设计人员对这样的界面非常熟悉,对其优雅而强大的功能感到非常欣慰。
强制体系结构的实施,作为设备的直接整合。Mac界面在另一个方面很值得注意。没有任何强迫,它的设计人员在所有的应用程序中使用标准界面,包括了大量的第三方程序。从而,用户在界面上获得的概念一致性不仅仅局限在机器所配备的软件,而且遍及所有的应用程序。
Mac设计人员把界面固化到只读内存中,从而开发者使用这些界面比开发自己的特殊界面更容易和快速。这些获取一致性的措施得到了足够广泛的应用,以致于可以形成实际的标准。Apple的管理投入和大量说服工作协助了这些措施。产品杂志中很多独立评论家,认识到了跨应用概念完整性的巨大价值,通过批评不遵从产品的反面例子,对上述方法进行了补充。
这是第6章中所推荐技术的一个非常杰出的例子,该技术通过鼓励其他人直接将某人的代码合并到自己的产品中来获得一致性,而不是试图根据某人的技术说明开发自己的软件。
WIMP的命运:过时被淘汰。尽管WIMP有很多优点,我仍期望WIMP界面在下一代技术中成为历史。如同我们支配我们机器一样,指针选取仍将是表达名词的方式,语音则无疑成为表达动词的方法。Mac上的Voice Navigator和PC上的Dragon已经提供了这种能力。
没有构建舍弃原型——瀑布模型是错误的!
一幅让人无法忘怀的图画,倒塌的塔科马大桥,开启了第11章。文中强烈地建议:“为舍弃而计划。无论如何,你一定要这样做。”现在我觉得这是错误的,并不是因为它太过极端,而是因为它太过简单。
在《未雨绸缪》一章体现的概念中,最大的错误是它隐含地假设了使用传统的顺序或者瀑布开发模型。该模型源自于类似甘特图布局的阶段化流程,常常绘制成图19.1的形状。Winton Royce在1970年的一篇经典论文中,改进了顺序模型,他提出:
存在一些从一个阶段到前一个阶段的反馈
将反馈限定在直接相邻的先前阶段,从而容纳它引起的成本增加和进度延迟
现场支持
系统测试
单元测试
编码
计划
图19.1:软件开发的瀑布模型
他给开发者提出的建议“构建两次”比《人月神话》的好8。受到瀑布模型不良影响并不只是第11章,而是从第2章的进度计划规则开始,贯穿了整本书。第2章中的经验法则分配了1/3的时间用于计划,1/6用于编码,1/4用于单元测试以及1/4用于系统测试。
瀑布模型的基本谬误是它假设项目只经历一次过程,而且体系结构出色并易于使用,设计是合理可靠的,随着测试的进行,编码实现是可以修改和调整的。换句话说,瀑布模型假设所有错误发生在编码实现阶段,因此它们的修复可以很顺畅地穿插在单元和系统测试中。
实际上,《未雨绸缪》并没有迎面痛击这个错误。它不是对错误的诊断,而是补救措施。现在,我建议应该一块块地丢弃和重新设计系统,而不是一次性地完成替换。就目前的情况而论,这没有问题,但它并没有触及问题的根本。瀑布模型把系统测试,以及潜在地把用户测试放在构件过程的末尾。因此,只有在投入了全部开发投资之后,才能发现无法接受的性能问题、笨拙功能以及察觉用户的错误或不当企图。不错,Alpha测试对规格说明的详细检查是为了尽早地发现这些缺陷,但是对于实际参与的用户却没有对应的措施。
瀑布模型的第二个谬误是它假设整个系统一次性地被构建,在所有的设计、大部分编码、部分单元测试完成之后,才为闭环的系统测试合并各个部分。
瀑布模型,这个大多数人在1975年考虑的软件项目开发方法,不幸地被奉为军用标准DOD-STD-2167,作为所有国防部军用软件的规范。所以,在大多数有见地的从业者认识到瀑
布模型的不完备并放弃之后,它仍然得以幸存。幸运的是,DoD也已经慢慢察觉到这一点9。
必须存在逆向移动。就像本章开始图片中精力充沛的大马哈鱼一样,在开发过程“下游”的经验和想法必须跃行而上,有时会超过一个阶段,来影响“上游”的活动。
例如,设计实现会发觉有些体系结构的功能定义会削弱性能,从而体系结构必须重新调整。编码实现会发现一些功能会使空间剧增,超过要求,因此必须更改体系结构定义和设计实现。
所以,在把任何东西实现成代码之前,可能要往复迭代两个或更多的体系结构-设计-实现循环。
增量开发模型更佳——渐进地精化
构建闭环的框架系统
从事实时系统开发的Harlan Mills,早期曾提倡我们首先应该构建实时系统的基本轮询回路,为每个功能都提供子函数调用(占位符),但仅仅是空的子函数(图19.2)。对它进行编译、测试,使它可以不断运行。它不直接完成任何事情,但至少是正常运行的10。
注:
MAIN LOOP-主循环
Subroutines-子函数
图19.2
接着,我们添加(可能是基本的)输入模块和输出模块。瞧,一个可运行的系统出现了,尽管只是一个框架。然后,一个功能接一个功能,我们逐渐开发和增加相应模块。在每个阶段,我们都拥有一个可运行的系统。如果我们非常勤勉,那么每个阶段都会有一个经过
调试和测试的系统。(随着系统的增长,使用所有先前的测试用例对每个新模块进行的回归测试也采用这种方式进行。)
在每个功能基本可以运行之后,我们一个接一个地精化或者重写每个模块——增量地开发(growing)整个系统。不过,我们有时的确需要修改原有的驱动回路,或者甚至是回路的模块接口。
因为我们在所有时刻都拥有一个可运行的系统,所以
我们可以很早开始用户测试,以及
我们可以采用按预算开发的策略,来彻底保证不会出现进度或者预算的超支(以允许的功能牺牲作为代价)。
我曾在北卡罗来纳大学教授软件工程实验课程22年,有时与David Parnas一起。在这门课程中,通常4名学生的团队会在一个学期内开发某个真正的实时软件应用系统。大约是一半的时候,我转而教授增量开发的课程。我常常会被屏幕上第一幅图案、第一个可运行的系统对团队士气产生的鼓舞效果而感到震惊。
Parnas产品族
在这整个20年的时间里,David Parnas曾是软件工程思潮的带头人。每个人对他的信息隐藏概念都很熟悉,但对他另一个非常重要的概念——将软件作为一系列相关的产品族来设计11——相对了解较少。Parnas力劝设计人员对产品的后期扩展和后续版本进行预测,定义它们在功能或者平台上的差异,从而搭建一棵相关产品的家族树(图19.3)。
设计类似一棵树的技巧是将那些变化可能性较小的设计决策放置在树的根部。
这样的设计使得模块的重用最大化。更重要的是,可以延伸相同的策略,使它不但可以包括发布产品,而且还包括以增量开发策略创建的后续中间版本。这样,产品可以通过它的中间阶段,以最低限度的回溯代价增长。
功能修改
移植到其他平台
第二代产品
内部版本
基产品
图19.3
Microsoft的“每晚重建”方法
Jams McCarthy向我描述了他的队伍和微软其他团队所使用的产品开发流程,这实际上是一种逻辑上的增量式开发。他说,
在我们第一次产品发布之后,我们会继续发布后续版本,向已有的可运行系统添加更多的功能。为什么最初的构建过程要不一样呢?因此,从我们第一个里程碑开始[第一次发布有三个里程碑],我们每晚重建开发中的系统[以及运行测试用例]。该构建周期成了项目的“心跳”。每天,一个和多个程序员-测试员队伍提交若干具有新功能的模块。在每次重建之后,我们会获得一个可运行的系统。如果重建失败,我们将停下整个过程,直到找到问题所在并进行修复。在任何时间,团队中的每个人都了解项目的状态。
这是非常困难的。你必须投入大量的资源,而且它是一个规范化、可跟踪、开诚布公的流程。它向团队提供了自身的可信度,而可信度决定了你的士气和情绪状态。
其他组织的软件开发人员对这个过程感到惊讶,甚至震惊。其中一个人说:“我们可以实现每周一次的重建,但是如果每晚一次的话,我想不大可能,工作量太大了。”这可能是对的。例如,Bell北方研究所就是每周重建1千2百万行的系统。
增量式开发和快速原型
增量开发过程能使真正的用户较早地参与测试,那么它与快速原型之间的区别是什么呢?我认为它们既是互相关联,又是相互独立的。各自可以不依赖对方而存在。
Harel将原型精彩地定义成:
仅仅反映了概念模型准备过程中所做的设计决策[的一个程序版本],它并未反映受实现考虑所驱使的设计决策12。
构建一个完全不属于发布产品的原型是完全可能的。例如,可以开发一个界面原型,但是并不包含任何的实际功能,而仅仅是一个看上去履行了各个步骤的有限状态机。甚至可以通过模拟系统响应的向导技术来原型化和测试界面。这种原型化对获取早期的用户反馈非常有用,但是它和产品发布前的测试区别很大。
类似的,实现人员可能会着手开发产品的某一块,并完整地实现该部分的有限功能集合,从而可以尽早发现性能上的潜在问题。那么,“从第一个里程碑开始构建”的微软流程和快速原型之间的差别是什么?功能。第一个里程碑产品可能不包含足够的功能使任何人对它产生兴趣,而可发布产品和定义中的一样,在完整性上——配备了一系列实用的功能集,在质量上——它可以健壮地运行。
关于信息隐藏,Parnas是正确的,我是错误的
在第7章中,关于每个团队成员应该在多大程度上被允许和鼓励相互了解设计和代码的问题,我对比了两种方法。在操作系统OS/360项目中,我们决定所有的程序员应该了解所有的材料——每个项目成员都拥有一份大约10,000页的项目工作手册拷贝。Harlan Mills颇有说服力地指出“编程是个开放性的公共过程”。把所有工作都暴露在每个人的凝视之下,能够帮助质量控制,这既源于其他人优秀工作的压力,也由于同伴能直接发现缺陷和bug。
这个观点和David Parnas的观点形成了鲜明的对比。David Parnas认为代码模块应该采用定义良好的接口来封装,这些模块的内部结构应该是程序员的私有财产,外部是不可见的。编程人员被屏蔽而不是暴露在他人模块内部结构面前。这种情况下,工作效率最高13。
我在第7章中并不认同Parnas的概念是“灾难的处方”。但是,Parnas是正确的,我是错误的。现在,我确信信息隐藏——现在常常内建于面向对象的编程中——是唯一提高软
件设计水平的途径。
实际上,任何技术的使用都可能演变成灾难。Mill的技术是通过了解接口另一侧的情况,使编程人员能理解他们所工作接口的详细语义。这些接口的隐藏会导致系统的bug。Parnas的技术在面对变更时是很健壮的,更加适合为变更设计的理念。