一个许多人痛苦挣扎的焦油坑以及一种乐趣和苦恼共存的创造性活动。
-
简单、武断地重复一下 Brooks 法则: 向进度落后的项目中增加人手,只会使进度更加落后。(Adding manpower to a late
software project makes it later)
-
项目的时间依赖于顺序上的限制,人员的数量依赖于 单个子任务的数量。从这两个数值可以推算出进度时间表,该表安排的人员较少,花费的时 间较长(唯一的风险是产品可能会过时)。相反,分派较多的人手,计划较短的时间,将无 法得到可行的进度表。总之,在众多软件项目中,缺乏合理的时间进度是造成项目滞后的最 主要原因,它比其他所有因素加起来的影响还要大。
-
这些研究表明,效率高和效率低的实施者之间具体差别非常大,经常达到了数量级的水平。
-
团队构成与分工
-
外科医生。Mills 称之为首席程序员。他亲自定义功能和性能技术说明书,设计程序, 编制源代码,测试以及书写技术文档。他使用例如 PL/I 的结构化编程语言,拥有对计算机 系统的访问能力;该计算机系统不仅仅能进行测试,还存储程序的各种版本,以允许简单的 文件更新,并对他的文档提供文本编辑能力。首席程序员需要极高的天分、十年的经验和应 用数学、业务数据处理或其他方面的大量系统和应用知识。
-
副手。他是外科医生的后备,能完成任何一部分工作,但是相对具有较少的经验。他 的主要作用是作为设计的思考者、讨论者和评估人员。外科医生试图和他沟通设计,但不受 到他建议的限制。副手经常在与其他团队的功能和接口讨论中代表自己的小组。他需要详细 了解所有的代码,研究设计策略的备选方案。显然,他充当外科医生的保险机制。他甚至可 能编制代码,但针对代码的任何部分,不承担具体的开发职责。
-
管理员。外科医生是老板,他必须在人员、加薪等方面具有决定权,但他决不能在这 些事务上浪费任何时间。因而,他需要一个控制财务、人员、工作地点安排和机器的专业管 理人员,该管理员充当与组织中其他管理机构的接口。Baker 建议仅在项目具有法律、合同、 报表和财务方面的需求时,管理员才具有全职责任。否则,一个管理员可以为两个团队服务。
-
编辑。外科医生负责产生文档——出于最大清晰度的考虑,他必须书写文档。对内部 描述和外部描述都是如此。而编辑根据外科医生的草稿或者口述的手稿,进行分析和重新组 织,提供各种参考信息和书目,对多个版本进行维护以及监督文档生成的机制。
-
两个秘书。管理员和编辑每个人需要一个秘书。管理员的秘书负责项目的协作一致和 非产品文件。
-
程序职员。他负责维护编程产品库中所有团队的技术记录。该职员接受秘书性质的培 训,承担机器码文件和可读文件的相关管理责任。
所有的计算机输入汇集到这个职员处。如果需要,他会对它们进行记录或者标识。输 出列表会提交给程序职员,由他进行归档和编制索引。另外,他负责将任何模型的最新运行 情况记录在状态日志中,而所有以前的结果则按时间顺序进行归档保存。
-
工具维护人员。现在已经有很多文件编辑、文本编辑和交互式调试等工具,因此团队 很少再需要自己的机器和机器操作人员。但是这些工具使用起来必须毫无疑问地令人满意, 而且需要具备较高的可靠性。外科医生则是这些工具、服务可用性的唯一评判人员。他需要 一个工具维护人员,保证所有基本服务的可靠性,以及承担团队成员所需要的特殊工具(特 别是交互式计算机服务)的构建、维护和升级责任。即使已经拥有非常卓越的、可靠的集中 式服务,每个团队仍然要有自己的工具人员。因为他的工作是检查他的外科医生所需要的工 具。工具维护人员常常要开发一些实用程序、编制具有目录的过程库以及宏库。
-
测试人员。外科医生需要大量合适的测试用例,用来对他所编写的工作片段,以及对 整个工作进行测试。因此,测试人员既是为他的各个功能设计系统测试用例的对头,同时也 是为他的日常调试设计测试数据的助手。他还负责计划测试的步骤和为测试搭建测试平台。
-
语言专家。随着 Algol 语言的出现,人们开始认识到大多数计算机项目中,总有一两 个乐于掌握复杂编程语言的人。这些专家非常有帮助,很快大家会向他咨询。这些天才不同 于外科医生,外科医生主要是系统设计者以及考虑系统的整体表现。而语言专家则寻找一种 简洁、有效的使用语言的方法来解决复杂、晦涩或者棘手的问题。他通常需要对技术进行一 些研究(两到三天)。通常一个语言专家可以为两个到三个外科医生服务。
-
-
运作方式
- 在外科手术团队中,外科医生和 副手都了解所有的设计和全部的代码。这节省了空间分配、磁盘访问等的劳动量,同时也确 保了工作概念上的完整性。
- 外科手术团队中,不存 在利益的差别,观点的不一致由外科医生单方面来统一
- 概念的完整性要求设计必须由一个人,或者非常少数互有默契的人员来实现。
- 系统的结构师,如同建筑的结构师一样,是用户的代理人。结构师的工作,是 运用专业技术知识来支持用户的真正利益,而不是维护销售人员所鼓吹的利益。
- 系统的概念完整 性决定了使用的容易程度。不能与系统基本概念进行整合的良好想法和特色,最好放到一边, 不予考虑
- 概念的完整性的确要求系统只反映唯一的设计理念,用户所见的技术说明来自少数人 的思想。实际工作被划分成体系结构、设计实现和物理实现,但这并不意味着该开发模式下 的系统需要更长的时间来创建。经验显示恰恰相反,整个系统将会开发得更快,所需要的测 试时间将更少。同工作的水平分割相比,垂直划分从根本上大大减少了劳动量,结果是使交 流彻底地简化,概念完整性得到大幅提高。
- 牢记是开发人员承担创造性和发明性的实现责任,所以结构师只能建议,而不能支 配;
- 时刻准备着为所指定的说明建议一种实现的方法,同样准备接受其他任何能达到目 标的方法;
- 对上述的建议保持低调和平静;
- 准备放弃坚持所作的改进建议;
- 一般开发人员会反对体系结构上的修改建议。通常他是对的—---—当正在实现产品时, 某些特性的修改会造成意料不到的成本开销。
- 在开发第一个系统时,结构师倾向于精炼和简洁。他知道自己对正在进行的任务不够 了解,所以他会谨慎仔细地工作。
- 第二个系统是设计师们所设计的最危险的系统。而当他着手第三个或第四个系统时, 先前的经验会相互验证,得到此类系统通用特性的判断,而且系统之间的差异会帮助他识别 出经验中不够通用的部分。
- 一种普遍倾向是过分地设计第二个系统,向系统添加很多修饰功能和想法,它们曾在 第一个系统中被小心谨慎地推迟了
- 结构师如何避免画蛇添足——开发第二个系统所引起的后果(second-system effect)?是的,他无法跳过二次系统。但他可以有意识关注那些系统的特殊危险,运用特 别的自我约束准则,来避免那些功能上的修饰;根据系统基本理念及目的变更,舍弃一些功 能。
- 项目经理如何避免画蛇添足(second-system effect)?他必须坚持至少拥有两个系 统以上开发经验结构师的决定。
- 他只是坐在那里,嘴里说:“做这个!做那个!”当然,什么都不会发生,光说不做是没有用的。
- 文档化的规格说明——手册
- 形式化定义----一句古老的格言警告说:“决不要携带两个时钟出海,带一个或三个。”同样的原则也 适用于形式化和记叙性定义。
- 直接整合
- 会议和大会
- 多重实现
- 电话日志
- 产品测试
-
如果项目有 n 个工作人员,则有(n2 - n)/ 2 个相互交流的接口,有将近 2n 个必须合 作的潜在团队。团队组织的目的是减少不必要交流和合作的数量,因此良好的团队组织是解 决上述交流问题的关键措施。
-
减少交流的方法是人力划分(division of labor)和限定职责范围(specialization of function)。当使用人力划分和职责限定时,树状管理结构所映出对详细交流的需要会相应减少,事实上,树状组织架构是作为权力和责任的结构出现。其基本原理——管理角色的非 重复性——导致了管理结构是树状的。但是交流的结构并未限制得如此严格,树状结构几乎 不能用来描述交流沟通,因为交流是通过网状结构进行的。在很多工程活动领域,树状模拟 结构不能很精确地用于描述一般团队、特别工作组、委员会,甚至是矩阵结构组织。
-
产品负责人的角色是什么?他组建团队,划分工作及制订进度表。他要求,并一直要 求必要的资源。这意味着他主要的工作是与团队外部,向上和水平地沟通。他建立团队内部 的沟通和报告方式。最后,他确保进度目标的实现,根据环境的变化调整资源和团队的构架。
那么技术主管的角色是什么?他对设计进行构思,识别系统的子部分,指明从外部看 上去的样子,勾画它的内部结构。他提供整个设计的一致性和概念完整性;他控制系统的复 杂程度。当某个技术问题出现时,他提供问题的解决方案,或者根据需要调整系统设计。用 Al Capp 所喜欢的一句谚语,他是“攻坚小组中的独行侠”(inside-man at the skunk works.)。 他的沟通交流在团队中是首要的。他的工作几乎完全是技术性的。
-
对小型的团队是最好的选择,如同在第 3 章《外科手术队伍》一 文中所述。对于真正大型项目中的一些开发队伍,我认为产品负责人作为管理者是更合适的 安排。
-
巴比伦塔可能是第一个工程上的彻底失败,但它不是最后一个。交流和交流的结果— —组织,是成功的关键。交流和组织的技能需要管理者仔细考虑,相关经验的积累和能力的 提高同软件技术本身一样重要。
- 对常用编程语句而言。生产率似乎是固定的。这个固定的生产率包括了编程中需要 注释,并可能存在错误的情况.
- 使用适当的高级语言,编程的生产率可以提高5倍。
- 由于规模是软件系统产品用户成本中如此大的一个组成部分,开发人员必须设置规模 的目标,控制规模,考虑减小规模的方法,就像硬件开发人员会设立元器件数量目标,控制 元器件的数量,想出一些减少零件的方法。同任何开销一样,规模本身不是坏事,但不必要 的规模是不可取的。
- 空间预算的多少和控制并不能使程序规模减小,为实现这一目标,它还需要一些创造 性和技能。
- 创造出自精湛的技艺,精炼、充分和快速的程序也是如此。技艺改进的结果往往是战 略上的突破,而不仅仅是技巧上的提高。这种战略上突破有时是一种新的算法,如快速傅立 叶变换,或者是将比较算法的复杂度从n2降低到n log n。
- 书面记录决策是必要的。只有记录下来,分歧才会明朗,矛盾才会突出。书写 这项活动需要上百次的细小决定,正是由于它们的存在,人们才能从令人迷惑的现象中得到 清晰、确定的策略。
- 文档能够作为同其他人的沟通渠道。项目经理常常会不断发现,许多理应被普 遍认同的策略,完全不为团队的一些成员所知。正因为项目经理的基本职责是使每个人都向 着相同的方向前进,所以他的主要工作是沟通,而不是做出决定。这些文档能极大地减轻他 的负担。
- 项目经理的文档可以作为数据基础和检查列表。通过周期性的回顾,他能清楚 项目所处的状态,以及哪些需要重点进行更改和调整。
- 普遍的做法是,选择一种方法,试试看;如果失败了,没关系,再试试别的。不管怎么样,重要的是先去尝试。
- 唯一不变的就是变化本身
- 为变更计划系统---变更的阶段化是一种必要的技术。每个产品都应该有数字版本号,每个版本都应该有 自己的日程表和冻结日期,在此之后的变更属于下一个版本的范畴。
- 为变更计划组织架构
- 巧匠因为他的工具而出名。
- 就工具而言,即使是现在,很多软件项目仍然像一家五金店。每个骨干人员都仔细地 保管自己工作生涯中搜集的一套工具集,这种方法对软件项目来说是愚蠢的。首先,项目的关键问题是沟通,个性化的工具妨 碍——而不是促进沟通。其次,当机器和语言发生变化时,技术也会随之变化,所有工具的 生命周期是很短的。毫无疑问,开发和维护公共的通用编程工具的效率更高。
- 机器支持可以有效地划分成目标机器和辅助机器。目标机器是软件所服务的对象,程 序必须在该机器上进行最后测试。辅助机器是那些在开发系统中提供服务的机器。如果是在 为原有的机型开发操作系统,则该机器不仅充当目标机器的角色,同时也作为辅助机器。
- 系统编程需要什么样的高级语言呢?现在可供合理选择的语言是 PL/I6。它提供完整的 功能集;它与操作系统环境相吻合;它有各种各样的编译器,一些是交互式的,一些速度很 快,一些诊断性很好,另一些能产生优化程度很高的代码。我自己觉得使用 APL 来解决算法 更快一些,然后,将它们翻译成某个系统环境下的 PL/I 语言。
- 剔除 bug 的设计
- 构件单元调试
- 系统集成调试
- 里程碑的选择只有一个原则,那就是,里程碑必须是具体的、特定的、可度量的事件, 能够进行清晰定义。
- 有两种掀开毯子把污垢展现在老板面前的方法,它们必须都被采用。一种是减少角色 冲突和鼓励状态共享,另一种是猛地拉开地毯。
- 不同用户需要不同级别的文档。某些用户仅仅偶尔使用程序,有些用户必须依赖程序,还有一些用户必须根据环境和目的的变动对程序进行修改。
- 使用程序。每个用户都需要一段对程序进行描述的文字。可是大多数文档只提供了很 少的总结性内容,无法达到用户要求,就像是描绘了树木,形容了树叶,但却没有一副森林 的图案。
- 流程图是被吹捧得最过分的一种程序文档。事实上,很多程序甚至不需要流程图,很 少有程序需要一页纸以上的流程图。
- 现实中,流程图被鼓吹的程度远大于它们的实际作用。我从来没有看到过一个有经验 的编程人员,在开始编写程序之前,会例行公事地绘制详尽的流程图。在一些要求流程图的 组织中,流程图总是事后才补上。一些公司则很自豪地使用工具软件,从代码中生成这个“不 可缺少的设计工具”。我认为这种普遍经验并不是令人尴尬和惋惜的对良好实践的偏离(似 乎大家只能对它露出窘迫的微笑),相反,它是对技术的良好评判,向我们传授了一些流程 图用途方面的知识。
没有任何技术或管理上的进展,能够独立地许诺十年内使生产率、可靠性或简洁性获得数量 级上的进步。
- 如果这是事实,那么软件开发总是非常困难的。天生就没有银弹。让我们来考虑现代软件系统中这些无法规避的内在特性:
- 复杂度、一致性、可变性和 不可见性。复杂度。规模上,软件实体可能比任何由人类创造的其他实体要复杂,因为没有任何 两个软件部分是相同的(至少是在语句的级别)。
- 某些情况下,因为是开发最新的软件,所以它必须遵循各种接口。另一些情况下,软 件的开发目标就是兼容性。在上述的所有情况中,很多复杂性来自保持与其他接口的一致, 对软件的任何再设计,都无法简化这些复杂特性
- 可变性。软件实体经常会遭受到持续的变更压力。
- 不可见性。软件是不可见的和无法可视化的。例如,几何抽象是强大的工具。
- 以往解决次要困难的一些突破
- 高级语言。
- 分时。
- 统一编程环境。
- 面向对象编程。---软件的内在问题是设计的复 杂度,上述方法并没有对它有任何的促进。除非我们现在的编程语言中,不必要的低层次类 型说明占据了软件产品设计 90%,面向对象编程才能带来数量级上的提高。对面向对象编 程这颗“银弹”,我深表怀疑。
- 人工智能。---专家系统最强有力的贡献是给缺乏经验的开发人员提供服务,用最优秀开发者的经验 和知识积累为他们提供了指导。这是非常大的贡献。最优秀和一般的软件工程实践之间的差 距是非常大的,可能比其他工程领域中的差距都要大,一种传播优秀实践的工具特别重要。
- “自动”编程。
- 图形化编程。
- 程序验证。---现代编程的许多工作是测试和修复 bug。是否有可能出现银弹,能够在系统 设计级别、源代码级别消除 bug 呢?是否可以在大量工作被投入到实现和测试之前,通过采用证实设计正确性的“深奥”策略,彻底提高软件的生产率和产品的可靠性?更严肃地说,完美的程序验证只能建立满足技术说明的程序。这时,软件工作中最困 难的部分已经接近完成,形成了完整和一致的说明。开发程序的一些必要工作实际上已经变 成对技术规格说明进行测试。
- 环境和工具。向更好的编程开发环境开发中投入,我们可以期待得到多少回报呢?人 们的本能反应是首先着手解决高回报的问题:层次化文件系统,统一文件格式以获得一致的 编程接口和通用工具等。特定语言的智能化编辑器在现实中还没有得到广泛应用,不过它们 最有希望实现的是消除语法错误和简单的语义错误。
- 工作站。随着工作站的处理能力和内存容量的稳固和快速提高,我们能期望在软件领 域取得多大的收获呢?现在的运算速度已经可以完全满足程序编制和文档书写的需要。编译 还需要一些提高,不过一旦机器运算速度提高十倍,那么程序开发人员的思考活动将成为日 常工作的主要活动。实际上,这已经是现在的情况。
- 购买和自行开发。构建软件最可能的彻底解决方案是不开发任何软件。
- 重大的变化在于计算机硬件/软件成本比率。
- 需求精炼和快速原型。
- 增量开发——增长,而非搭建系统。
- 卓越的设计人员。
- 尽可能早地、有系统地识别顶级的设计人员。最好的通常不是那些最有经验的人员。
- 为设计人员指派一位职业导师,负责他们技术方面的成长,仔细地为他们规划职业生涯。
- 为每个方面制订和维护一份职业计划,包括与设计大师的、经过仔细挑选的学习过 程、正式的高级教育和以及短期的课程——所有这些都穿插在设计和技术领导能力的培养安 排中。
- 为成长中的设计人员提供相互交流和学习的机会。
那些想看到完美方案的人,其实在心底里就认为它们以前不存在,以后也不可能出现。
- 《没有银弹》中声称和断定,在近十年内,没有任何单独的软件工程进展可以使软件 生产率有数量级的提高(引自 1986 年的版本)。现在已经是第九个年头,因此也该看看是否 这些预言得到了应验。
- 对于十年的期限,还有其他的一些原因:各种银弹都宣称它们能够立刻取得效果。我 回顾了一下,发现没有任何一种银弹声称“向我的秘方投资,在十年后你将获得成功”。另 外,硬件的性能/价格比可能每十年就会有成百倍的增长,尽管这种比较不很合适,但是直 觉上的确如此。我们确信会在下一个 40 年中取得稳步的发展。不过,以 40 年代价取得数量 级的进展,很难被认为是不可思议的进步。
- 解决软件构建根本困难的最佳方法是不进行任何开发。软件包只是达到上述目标的方 法之一,另外的方法是程序重用。实际上,类的容易重用和通过继承方便地定制是面向对象 技术最吸引人的地方。
- 子弹的本质——形势没有发生改变-----复杂性是我们这个行业的属性,而且复杂性是我们主要的限制。
第 1 章 焦油坑
1.1 编程系统产品(Programming Systems Product)开发的工作量是供个人使用的、 独立开发的构件程序的九倍。我估计软件构件产品化引起了 3 倍工作量,将软件构件整合成 完整系统所需要的设计、集成和测试又强加了 3 倍的工作量,这些高成本的构件在根本上是 相互独立的。
1.2 编程行业“满足我们内心深处的创造渴望和愉悦所有人的共有情感”,提供了五种乐趣:
- 创建事物的快乐
- 开发对其他人有用的东西的乐趣
- 将可以活动、相互啮合的零部件组装成类似迷宫的东西,这个过程所体现出令人 神魂颠倒的魅力
- 面对不重复的任务,不间断学习的乐趣
- 工作在如此易于驾驭的介质上的乐趣——纯粹的思维活动,其存在、移动和运转
方式完全不同于实际物体
1.3 同样,这个行业具有一些内在固有的苦恼:
将做事方式调整到追求完美,是学习编程的最困难部分
由其他人来设定目标,并且必须依靠自己无法控制的事物(特别是程序);权威 不等同于责任
实际情况看起来要比这一点好一些:真正的权威来自于每次任务的完成
任何创造性活动都伴随着枯燥艰苦的劳动,编程也不例外
人们通常期望项目在接近结束时,(bug、工作时间)能收敛得快一些,然而软件 项目的情况却是越接近完成,收敛得越慢
产品在即将完成时总面临着陈旧过时的威胁
第2章 人月神话 2.1 缺乏合理的时间进度是造成项目滞后的最主要原因,它比其他所有因素加起来影响还大。 2.2 良好的烹饪需要时间,某些任务无法在不损害结果的情况下加快速度。 2.3 所有的编程人员都是乐观主义者:“一切都将运作良好”。 2.4 由于编程人员通过纯粹的思维活动来开发,所以我们期待在实现过程中不会碰到困难。
2.6 我们围绕成本核算的估计技术,混淆了工作量和项目进展。人月是危险和带有欺骗 性的神话,因为它暗示人员数量和时间是可以相互替换的。
2.7 在若干人员中分解任务会引发额外的沟通工作量——培训和相互沟通。 2.8 关于进度安排,我的经验是为 1/3 计划、1/6 编码、1/4 构件测试以及 1/4 系统测试。
2.10 因为我们对自己的估计技术不确定,所以在管理和客户的压力下,我们常常缺 乏坚持的勇气。
2.11 Brook 法则:向进度落后的项目中增加人手,只会使进度更加落后。 2.12 向软件项目中增派人手从三个方面增加了项目必要的总体工作量:任务重新分配本身和所造成的工作中断;培训新人员;额外的相互沟通。
第 3 章 外科手术队伍 3.1 同样有两年经验而且在受到同样的培训的情况下,优秀的专业程序员的工作效率是
较差程序员的十倍。(Sackman、Erikson 和 Grand) 3.2 Sackman、Erikson 和 Grand 的数据显示经验和实际表现之间没有相互联系。我怀疑这种现象是否普遍成立。
3.3 小型、精干队伍是最好的——尽可能的少。
3.4 两个人的团队,其中一个项目经理,常常是最佳的人员使用方法。[留意一下上帝 对婚姻的设计。]
3.5 对于真正意义上的大型系统,小型精干的队伍太慢了。
3.6 实际上,绝大多数大型编程系统的经验显示出,一拥而上的开发方法是高成本、速 度缓慢、不充分的,开发出的产品无法进行概念上的集成。
2.5 但是,我们的构思是有缺陷的,因此总会有 bug。
2.9 作为一个学科,我们缺乏数据估计。
3.7 一位首席程序员、类似于外科手术队伍的团队架构提供了一种方法——既能获得由 少数头脑产生的产品完整性,又能得到多位协助人员的总体生产率,还彻底地减少了沟通的 工作量。
第 4 章 贵族专制、民主政治和系统设计
4.1 “概念完整性是系统设计中最重要的考虑因素”。
4.2 “功能与理解上的复杂程度的比值才是系统设计的最终测试标准”,而不仅仅是丰 富的功能。[该比值是对易用性的一种测量,由简单和复杂应用共同验证。]
4.3 为了获得概念完整性,设计必须由一个人或者具有共识的小型团队来完成。 4.4 “对于非常大型的项目,将设计方法、体系结构方面的工作与具体实现相分离是获得概念完整性的强有力方法。”[同样适用于小型项目。] 4.5 “如果要得到系统概念上的完整性,那么必须控制这些概念。这实际上是一种无需任何歉意的贵族专制统治。”
4.6 纪律、规则对行业是有益的。外部的体系结构规定实际上是增强,而不是限制实现 小组的创造性。
4.7 概念上统一的系统能更快地开发和测试。 4.8 体系结构(architecture)、设计实现(implementation)、物理实现(realization)的许多工作可以并发进行。[软件和硬件设计同样可以并行。]
第5章 画蛇添足 5.1 尽早交流和持续沟通能使结构师有较好的成本意识,以及使开发人员获得对设计的信心,并且不会混淆各自的责任分工。 5.2 结构师如何成功地影响实现: 牢记是开发人员承担创造性的实现责任;结构师只能提出建议。 时刻准备着为所指定的说明建议一种实现的方法,准备接受任何其他可行的方法。
对上述的建议保持低调和平静。
准备对所建议的改进放弃坚持。
听取开发人员在体系结构上改进的建议。
5.3 第二个系统是人们所设计的最危险的系统,通常的倾向是过分地进行设计。
5.4 OS/360 是典型的画蛇添足(second-system effect)的例子。[Windows NT 似乎是 90 年代的例子。]
5.5 为功能分配一个字节和微秒的优先权值是一个很有价值的规范化方法。
第6章 贯彻执行 6.1 即使是大型的设计团队,设计结果也必须由一个或两个人来完成,以确保这些决定是一致的。
6.2 必须明确定义体系结构中与先前定义不同的地方,重新定义的详细程度应该与原先 的说明一致。
6.3 出于精确性的考虑,我们需要形式化的设计定义,同样,我们需要记叙性定义来加 深理解。
6.4 必须采用形式化定义和记叙性定义中的一种作为标准,另一种作为辅助措施;它们 都可以作为表达的标准。
6.5 设计实现,包括模拟仿真,可以充当一种形式化定义的方法;这种方法有一些严重 的缺点。
6.6 直接整合是一种强制推行软件的结构性标准的方法。[硬件上也是如此——考虑内 建在ROM中的Mac WIMP接口。]
6.7 “如果起初至少有两种以上的实现,那么(体系结构)定义会更加整洁,会更加规 范。”
6.8 允许体系结构师对实现人员的询问做出电话应答解释是非常重要的,并且必须进行日志记录和整理发布。[电子邮件是一种可选的介质。] 6.9 “项目经理最好的朋友就是他每天要面对的敌人——独立的产品测试机构/小组。”
第 7 章 为什么巴比伦塔会失败? 7.1 巴比伦塔项目的失败是因为缺乏交流,以及交流的结果——组织。交流
7.2 “因为左手不知道右手在做什么,从而进度灾难、功能的不合理和系统缺陷纷纷出 现。”由于对其他人的各种假设,团队成员之间的理解开始出现偏差。
7.3 团队应该以尽可能多的方式进行相互之间的交流:非正式、常规项目会议,会上进 行简要的技术陈述、共享的正式项目工作手册。[以及电子邮件。]项目工作手册
7.4 项目工作手册“不是独立的一篇文档,它是对项目必须产生的一系列文档进行组织 的一种结构。”
7.5 “项目所有的文档都必须是该(工作手册)结构的一部分。”
7.6 需要尽早和仔细地设计工作手册结构。
7.7 事先制订了良好结构的工作手册“可以将后来书写的文字放置在合适的章节中”, 并且可以提高产品手册的质量。
7.8 “每一个团队成员应该了解所有的材料(工作手册)。”[我想说的是,每个团队成 员应该能够看到所有材料,网页即可满足要求。]
7.9 实时更新是至关重要的。
7.10 工作手册的使用者应该将注意力集中在上次阅读后的变更,以及关于这些变更 重要性的评述。
- 7.11 OS/360 项目工作手册开始采用的是纸介质,后来换成了微缩胶片。
- 7.12 今天[即使在 1975 年],共享的电子手册是能更好达到所有这些目标、更加低廉、更加简单的机制。 7.13 仍然需要用变更条和修订日期[或具备同等功能的方法]来标记文字;仍然需要后进先出(LIFO)的电子化变更小结。 7.14 Parnas 强烈地认为使每个人看到每件事的目标是完全错误的;各个部分应该被封装,从而没有人需要或者允许看到其他部分的内部结构,只需要了解接口。 7.15 Parnas 的建议的确是灾难的处方。[Parnas 让我认可了该观点,使我彻底地改变了想法。]组织架构
7.16 团队组织的目标是为了减少必要的交流和协作量。
7.17 为了减少交流,组织结构包括了人力划分(division of labor)和限定职责 范围(specialization of function)。
7.18 传统的树状组织结构反映了权力的结构原理——不允许双重领导。 7.19 组织中的交流是网状,而不是树状结构,因而所有的特殊组织机制(往往体现成组织结构图中的虚线部分)都是为了进行调整,以克服树状组织结构中交流缺乏的困难。
7.20 每个子项目具有两个领导角色——产品负责人、技术主管或结构师。这两个角 色的职能有着很大的区别,需要不同的技能。
7.21 两种角色中的任意组合可以是非常有效的: 产品负责人和技术主管是同一个人。 产品负责人作为总指挥,技术主管充当其左右手。 技术主管作为总指挥,产品负责人充当其左右手。
第8章 胸有成竹 8.1 仅仅通过对编码部分的估计,然后乘以任务其他部分的相对系数,是无法得出对整项工作的精确估计的。
8.2 构建独立小型程序的数据不适用于编程系统项目。
8.3 程序开发呈程序规模的指数增长。
8.4 一些发表的研究报告显示指数约为 1.5。[Boehm 的数据并不完全一致,在 1.05 和 1.2 之间变化。1]
8.5 Portman 的 ICL 数据显示相对于其他活动开销,全职程序员仅将 50%的时间用于编 程和调试。
8.6 IBM 的 Aron 数据显示,生产率是系统各个部分交互的函数,在 1.5K 千代码行/人 年至 10K 千代码行/人年的范围内变化。
8.7 Harr 的 Bell 实验室数据显示对于已完成的产品,操作系统类的生产率大约是 0.6KLOC/人年,编译类工作的生产率大约为 2.2KLOC/人年。
8.8 Brooks 的 OS/360S 数据与 Harr 的数据一致:操作系统 0.60.8KLOC/人年,编译 器23 KLOC/人年。
8.9 Corbato 的 MIT 项目 MULTICS 数据显示,在操作系统和编译器混合类型上的生产率 是 1.2KLOC/人年,但这些是 PL/I 的代码行,而其他所有的数据是汇编代码行。
8.10 在基本语句级别,生产率看上去是个常数。 8.11 当使用适当的高级语言时,程序编制的生产率可以提高 5 倍。
第9章 削足适履 9.1 除了运行时间以外,所占据的内存空间也是主要开销。特别是对于操作系统,它的很多程序是永久驻留在内存中。
9.2 即便如此,花费在驻留程序所占据内存上的金钱仍是物有所值的,比其他任何在配 置上投资的效果要好。规模本身不是坏事,但不必要的规模是不可取的。
9.3 软件开发人员必须设立规模目标,控制规模,发明一些减少规模的方法——就如同 硬件开发人员为减少元器件所做的一样。
9.4 规模预算不仅仅在占据内存方面是明确的,同时还应该指明程序对磁盘的访问次 数。
9.5 规模预算必须与分配的功能相关联;在指明模块大小的同时,确切定义模块的功能。 9.6 在大型的团队中,各个小组倾向于不断地局部优化,以满足自己的目标,而较少考虑队用户的整体影响。这种方向性的问题是大型项目的主要危险。
9.7 在整个实现的过程期间,系统结构师必须保持持续的警觉,确保连贯的系统完整性。
9.8 培养开发人员从系统整体出发、面向用户的态度是软件编程管理人员最重要的职 能。
9.9 在早期应该制订策略,以决定用户可选项目的粗细程度,因为将它们作为整体大包 能够节省内存空间。[常常还可以节约市场成本。]
9.10 临时空间的尺寸,以及每次磁盘访问的程序数量是很关键的决策,因为性能是 规模的非线性函数。[这个整体决策已显得过时——起初是由于虚拟内存,后来则是成本低 廉的内存。现在的用户通常会购买能容纳主要应用程序所有代码的内存。]
9.11 为了取得良好的空间-时间折衷,开发队伍需要得到特定与某种语言或者机型 的编程技能培训,特别是在使用新语言或者新机器时。
9.12 编程需要技术积累,每个项目需要自己的标准组件库。 9.13 库中的每个组件需要有两个版本,运行速度较快和短小精炼的。[现在看来有些过时。] 9.14 精炼、充分和快速的程序。往往是战略性突破的结果,而不仅仅技巧上的提高。 9.15 这种突破常常是一种新型算法。
9.16 更普遍的是,战略上突破常来自于数据或表的重新表达。数据的表现形式是编 程的根本。
第 10 章 提纲挈领 10.1 “前提:在一片文件的汪洋中,少数文档形成了关键的枢纽,每个项目管理的
工作都围绕着它们运转。它们是经理们的主要个人工具。”
10.2 对于计算机硬件开发项目,关键文档是目标、手册、进度、预算、组织机构图、 空间分配、以及机器本身的报价、预测和价格。
10.3 对于大学科系,关键文档类似:目标、课程描述、学位要求、研究报告、课程 表和课程的安排、预算、教室分配、教师和研究生助手的分配。
10.4 对于软件项目,要求是相同的:目标、用户手册、内部文档、进度、预算、组 织机构图和工作空间分配。
10.5 因此,即使是小型项目,项目经理也应该在项目早期规范化上述的一系列文档。
10.6 以上集合中每一个文档的准备工作都将注意力集中在对讨论的思索和提炼,而 书写这项活动需要上百次的细小决定,正是由于它们的存在,人们才能从令人迷惑的现象中 得到清晰、确定的策略。
10.7 对每个关键文档的维护提供了状态监督和预警机制。
10.8 每个文档本身就可以作为检查列表或者数据库。
10.9 项目经理的基本职责是使每个人都向着相同的方向前进。
10.10 项目经理的主要日常工作是沟通,而不是做出决定;文档使各项计划和决策在 整个团队范围内得到交流。
10.11 只有一小部分管理人员的时间——可能只有 20%——用来从自己头脑外部获 取信息。
10.12 出于这个原因,广受吹捧的市场概念——支持管理人员的“完备信息管理系统” 并不基于反映管理人员行为的有效模型。
第 11 章 未雨绸缪
11.1 化学工程师已经认识到无法一步将实验室工作台上的反应过程移到工厂中,需 要一个实验性工厂(pilot planet)来为提高产量和在缺乏保护的环境下运作提供宝贵经验。
11.2 对于编程产品而言,这样的中间步骤是同样必要的,但是软件工程师在着手发 布产品之前,却并不会常规地进行试验性系统的现场测试。[现在,这已经成为了一项普遍 的实践,beta 版本。它不同于有限功能的原型,alpha 版本,后者同样是我所倡导的实践。]
11.3 对于大多数项目,第一个开发的系统并不合用。它可能太慢、太大,而且难以 使用,或者三者兼而有之。
11.4 系统的丢弃和重新设计可以一步完成,也可以一块块地实现。这是个必须完成 的步骤。
11.5 将开发的第一个系统——丢弃原型——发布给用户,可以获得时间,但是它的 代价高昂——对于用户,使用极度痛苦;对于重新开发的人员,分散了精力;对于产品,影 响了声誉,即使最好的再设计也难以挽回名声。
11.6 因此,为舍弃而计划,无论如何,你一定要这样做。
11.7 “开发人员交付的是用户满意程度,而不仅仅是实际的产品。”(Cosgrove)
11.8 用户的实际需要和用户感觉会随着程序的构建、测试和使用而变化。
11.9 软件产品易于掌握的特性和不可见性,导致了它的构建人员(特别容易)面临 着永恒的需求变更。
11.10 目标上(和开发策略上)的一些正常变化无可避免,事先为它们做准备总比假 设它们不会出现要好得多。
11.11 为变更计划软件产品的技术,特别是细致的模块接口文档——非常地广为人 知,但并没有相同规模的实践。尽可能地使用表驱动技术同样是有所帮助的。[现在内存的 成本和规模使这项技术越来越出众。]
11.12 高级语言的使用、编译时操作、通过引用的声明整合和自文档技术能减少变更 引起的错误。
11.13 采用定义良好的数字化版本将变更量子(阶段)化。[当今的标准实践。]为变更计划组织架构 11.14 程序员不愿意为设计书写文档的原因,不仅仅是由于惰性。更多的是源于设计人员的踌躇——要为自己尝试性的设计决策进行辩解。(Cosgrove)
11.15 为变更组建团队比为变更进行设计更加困难。
11.16 只要管理人员和技术人才的天赋允许,老板必须对他们的能力培养给予极大的 关注,使管理人员和技术人才具有互换性;特别是希望能在技术和管理角色之间自由地分配 人手的时候。
11.17 具有两条晋升线的高效组织机构,存在着一些社会性的障碍,人们必须警惕和 积极地同它做持续的斗争。
11.18 很容易为不同的晋升线建立相互一致的薪水级别,但要同等威信的建立需要一 些强烈的心理措施:相同的办公室、一样的支持和技术调动的优先补偿。
11.19 组建外科手术队伍式的软件开发团队是对上述问题所有方面的彻底冲击。对于 灵活组织架构问题,这的确是一个长期行之有效的解决方案。前进两步,后退一步——程序维护
11.20 程序维护基本上不同于硬件的维护;它主要由各种变更组成,如修复设计缺陷、 新增功能、或者是使用环境或者配置变换引起的调整。
- 11.21 对于一个广泛使用的程序,其维护总成本通常是开发成本的 40%或更多。
- 11.22 维护成本受用户数目的严重影响。用户越多,所发现的错误也越多。
- 11.23 Campbell 指出了一个显示产品生命期中每月 bug 数的有趣曲线,它先是下降,然后攀升。
11.24 缺陷修复总会以(20-50)%的机率引入新的 bug。 11.25 在每次修复之后,必须重新运行先前所有的测试用例,从而确保系统不会以更
隐蔽的方式被破坏。
11.26 能消除、至少是能指明副作用的程序设计方法,对维护成本有很大的影响。
11.27 同样,设计实现的人员越少、接口越少,产生的错误也就越少。前进一步,后退一步——系统熵随时间增加
11.28 Lehman 和 Belady 发现模块数量随大型操作系统(OS/360)版本号的增加呈线 性增长,但是受到影响的模块以版本号指数的级别增长。
11.29 所有修改都倾向于破坏系统的架构,增加了系统的混乱程度。即使是最熟练的 软件维护工作,也只是放缓了系统退化到不可修复混乱的进程,从中必须要重新进行设计。 [许多程序升级的真正需要,如性能等,尤其会冲击它的内部结构边界。原有边界引发的不 足常常在日后才会出现。]
第 12 章 干将莫邪 12.1 项目经理应该制订一套策略,以及为通用工具的开发分配资源,与此同时,他还必须意识到专业工具的需求。
12.2 开发操作系统的队伍需要自己的目标机器,进行调试开发工作。相对于最快的 速度而言,它更需要最大限度的内存,还需要安排一名系统程序员,以保证机器上的标准软 件是即时更新和实时可用的。
12.3 同时还需要配备调试机器或者软件,以便在调试过程中,所有类型的程序参数 可以被自动计数和测量。
12.4 目标机器的使用需求量是一种特殊曲线:刚开始使用率非常低,突然出现爆发 性的增长,接着趋于平缓。
12.5 同天文工作者一样,系统调试总是大部分在夜间完成。 12.6 抛开理论不谈,一次分配给某个小组连续的目标时间块被证明是最好的安排方法,比不同小组的穿插使用更为有效。 12.7 尽管技术不断变化,这种采用时间块来安排匮乏计算机资源的方式仍得以延续
20 年[在 1975 年],是因为它的生产率最高。[在 1995 年依然如此]
12.8 如果目标机器是新产品,则需要一个目标机器的逻辑仿真装置。这样,可以更 快地得到辅助调试平台。即使在真正机器出现之后,仿真装置仍可提供可靠的调试平台。
12.9 主程序库应该被划分成(1)一系列独立的私有开发库;(2)正处在系统测试 下的系统集成子库;(3)发布版本。正式的分离和进度提供了控制。
12.10 在编制程序的项目中,节省最大工作量的工具可能是文本编辑系统。 12.11 系统文档中的巨大容量带来了新的不理解问题[例如,看看 Unix],但是它比大多数未能详细描述编程系统特性的短小文章更加可取。
12.12 自顶向下、彻底地开发一个性能仿真装置。尽可能早地开始这项工作,仔细地 听取 “它们表达的意见”。高级语言 12.13 只有懒散和惰性会妨碍高级语言和交互式编程的广泛应用。[如今它们已经在全世界使用。] 12.14 高级语言不仅仅提升了生产率,而且还改进了调试:bug 更少,以及更容易寻找。
12.15 传统的反对意见——功能、目标代码的尺寸、目标代码的速度,随着语言和编 译器技术的进步已不再成为问题。
12.16 现在可供合理选择的语言是 PL/I。[不再正确。]交互式编程
12.17 某些应用上,批处理系统决不会被交互式系统所替代。[依然成立。]
12.18 调试是系统编程中很慢和较困难的部分,而漫长的调试周转时间是调试的祸 根。
12.19 有限的数据表明了系统软件开发中,交互式编程的生产率至少是原来的两倍。
第 13 章 整体部分 13.1 第 4、5、6 章所意味的煞费苦心、详尽体系结构工作不但使产品更加易于使用,而且使开发更容易进行以及 bug 更不容易产生。 13.2 V.A.Vyssotsky 提出,“许许多多的失败完全源于那些产品未精确定义的地方。” 13.3 在编写任何代码之前,规格说明必须提交给测试小组,以详细地检查说明的完整性和明确性。开发人员自己不会完成这项工作。(Vyssotsky) 13.4 “十年内[1965~1975],Wirth 的自顶向下进行设计[逐步细化]将会是最重要的新型形式化软件开发方法。”
- 13.5 Wirth 主张在每个步骤中,尽可能使用级别较高的表达方法。
- 13.6 好的自顶向下设计从四个方面避免了 bug。
13.7 有时必须回退,推翻顶层设计,重新开始。
13.8 结构化编程中,程序的控制结构仅由支配代码块(相对于任意的跳转)的给定 集合所组成。这种方法出色地避免了 bug,是一种正确的思考方式。
13.9 Gold 结果显示了,在交互式调试过程中,第一次交互取得的工作进展是后续 交互的三倍。这实际上获益于在调试开始之前仔细地调试计划。[我认为在 1995 年依然如 此。]
13.10 我发现对良好终端系统的正确使用,往往要求每两小时的终端会话对应于两小 时的桌面工作:1 小时会话后的清理和文档工作;1 小时为下一次计划变更和测试。
13.11 系统调试(相对于单元测试)花费的时间会比预料的更长。
13.12 系统调试的困难程度证明了需要一种完备系统化和可计划的方法。
13.13 系统调试仅仅应该在所有部件能够运作之后开始。(这既不同于为了查出接口 bug 所采取 “合在一起尝试” 的方法;也不同于在所有构件单元的 bug 已知,但未修复的 情况下,即开始系统调试的做法。)[对于多个团队尤其如此。]
13.14 开发大量的辅助调试平台(scaffolding 脚手架)和测试代码是很值得的,代码量甚至可能会有测试对象的一半。
13.15 必须有人对变更进行控制和文档化,团队成员应使用开发库的各种受控拷贝来 工作。
13.16 系统测试期间,一次只添加一个构件。
13.17 Lehman 和 Belady 出示了证据,变更的阶段(量子)要么很大,间隔很宽;要 么小和频繁。后者很容易变得不稳定。[Microsoft 的一个团队使用了非常小的阶段(量子)。 结果是每天晚上需要重新编译生成增长中的系统。]
第 14 章 祸起萧墙
14.1 “项目是怎样延迟了整整一年的时间?⋯一次一天。”
14.2 一天一天的进度落后比起重大灾难,更难以识别、更不容易防范和更加难以弥 补。
14.3 根据一个严格的进度表来控制项目的第一个步骤是制订进度表,进度表由里程 碑和日期组成。
14.4 里程碑必须是具体的、特定的、可度量的事件,能进行清晰能定义。 14.5 如果里程碑定义得非常明确,以致于无法自欺欺人时,程序员很少会就里程碑的进展弄虚作假。
14.6 对于大型开发项目中的估计行为,政府的承包商所做的研究显示:每两周进行 仔细修订的活动时间估计,随着开始时间的临近不会有太大的变化;期间内对时间长短的过 高估计,会随着活动的进行持续下降;过低估计直到计划的结束日期之前大约三周左右,才 有所变化。
14.7 慢性进度偏离是士气杀手。[Microsoft 的 Jim McCarthy 说:“如果你错过了 一个最终期限(deadline),确保制订下一条 deadline。2”]
14.8 进取对于杰出的软件开发团队,同优秀的棒球队伍一样,是不可缺少的必要品 德。
14.9 不存在关键路径进度的替代品,使人们能够辨别计划偏移的情况。
14.10 PERT 的准备工作是 PERT 图使用中最有价值的部分。它包括了整个网状结构的 展开、任务之间依赖关系的识别、各个任务链的估计。这些都要求在项目早期进行非常专业 的计划。
14.11 第一份 PERT 图总是很恐怖的,不过人们总是不断进行努力,运用才智制订下 一份 PERT 图。
-
14.12 PERT 图为前面那个泄气的借口,“其他的部分反正会落后”,提供了答案。
-
14.13 每个老板同时需要采取行动的异常信息以及用来进行分析和早期预警的状态数据。
-
14.14 状态的获取是困难的,因为下属经理有充分的理由不提供信息共享。
-
14.15 老板的不良反应肯定会对信息的完全公开造成压制;相反,仔细区分状态报告、毫无惊慌地接收报告、决不越俎代庖,将能鼓励诚实的汇报。
14.16 必须有评审的机制,从而所有成员可以通过它了解真正的状态。出于这个目的, 里程碑的计划和完成文档是关键。
14.17 Vyssotsky:我发现在里程碑报告中很容易记录“计划(老板的日期)”和“估 计(最基层经理的日期)”的日期。项目经理必须停止对这些日期的怀疑。”
14.18 对于大型项目,一个对里程碑报告进行维护的计划和控制(Plan and Control) 小组是非常可贵的。
第 15 章 另外一面 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 软件工程的焦油坑在将来很长一段时间内会继续地使人们举步维艰,无法自拔。