第1章 焦油坑
编程系统产品

程序是基本的代码;编程产品是加上文档、调试和测试的程序;而编程系统产品则是为集成和与其他系统交互而设计的编程产品。在《人月神话》中,布鲁克斯强调,开发一个编程系统产品通常比简单编写一个程序要复杂得多,并且需要更多的时间和资源。
职业的乐趣
- 这是一种创建事物的纯粹的快乐。
- 这种快乐来自于开发对他人有用的东西。
- 快乐来自于整个过程体现出的一股强大的魅力——将相互啮合的零部件组装在一起,看到它们以精妙的方式运行着,并收到了预期的效果。
- 这种快乐是持续学习的快乐,它来自于这项工作的非重复特性。
- 这种快乐还来自于在易于驾驭的介质上工作。
职业的苦恼
- 苦恼来自于追求完美。学习编程最困难的部分,是将做事的方式向追求完美的方向调整。
- 苦恼来自由他人来设定目标、供给资源和提供信息。编程人员很少能控制工作环境和工作目标。
- 对其他人的依赖是一件非常痛苦的事情。依靠其他人的程序,而这些程序往往设计得并不合理、实现拙劣、发布不完整(没有源代码或测试用例)或者文档记录得很糟。
- 概念性设计是有趣的,但寻找琐碎的bug却是一项重复性的活动。
- 当投入了大量辛苦的劳动,产品在即将完成或者终于完成的时候,却已显得陈旧过时。
第2章 人月神话
在众多软件项目中,缺乏合理的进度安排是造成项目滞后的最主要原因,它比其他所有因素加起来的影响还要大。导致这种灾难的原因有如下:
- 对估算技术缺乏有效的研究。
- 采用的估算技术隐含地假设人和月可以互换,错误地将进度与工作量相互混淆。
- 由于对自己的估算缺乏信心,软件经理通常不会有耐心持续地估算这项工作。
- 对进度缺少跟踪和监督。
- 当意识到进度的偏移时,下意识的反应是增加人力。这就像使用汽油灭火一样,只会使事情更糟。
乐观主义
所有的编程人员都是乐观主义者。
系统编程的进度安排背后的第一个错误的假设是:一切都将运行良好,每一项任务仅花费它所“应该”花费的时间。
人月
第二个谬误的思考方式是:估计和进度安排中使用的工作量单位:人月。
用人月作为衡量一项工作的规模是一个危险和带有欺骗性的神话。它暗示着人员的数量和时间是可以相互替换的。

人数和时间的互换仅仅适用于以下情况:某个任务可以分解给参与人员,并且他们之间不需要相互的交流(图2-1)。这在割小麦或收获棉花的工作中是可行的:而在系统编程中近乎不可能。
当任务由于次序上的限制不能分解时,人手的添加对进度没有帮助(见图2-2)。
沟通所增加的负担由两个部分组成:培训和相互的交流。每个成员需要进行技术、项目目标、总体策略以及工作计划的培训。这种培训是不能分解的,因此这部分增加的工作量随人员的数量呈线性变化。

因为软件开发本质上是一项系统工作——错综复杂关系下的一种实践,沟通、交流的工作量非常大,它很快会消耗任务分解所节省下来的个人时间。从而,添加更多的人手,实际上是延长了而不是缩短了时间进度。
系统测试
作者的软件任务进度安排的经验法则:
- 1/3 计划
- 1/6 编码
- 1/4 构件测试和早期系统测试
- 1/4 系统测试,所有的构件已完成
不为系统测试安排足够的时间简直就是一场灾难。
空泛的估算
为了满足顾客期望的日期而造成的不合理进度安排,在软件领域中比其他任何工程领域要普遍得多。
有两种解决方法:
- 开发并推行生产率图标、缺陷率图表、估算规则等;
- 在基于可靠基础的估算出现之前,项目经验需要挺直腰杆,坚持他们的估计,确信自己的经验和直觉总比从期望派生出的结果要强得多。
重复产生的进度灾难
向进度落后的项目中增加人手,只会使进度更加落后。
在众多软件项目中,缺乏合理的进度安排是造成项目滞后的最主要原因,它比其他所有因素加起来的影响还要大。
第3章 外科手术队伍
问题
一拥而上的开发方法是高成本的、速度缓慢的、低效的,开发出的是无法在概念上进行集成的产品。
小型、精干的队伍,对于真正意义上的大型系统,它太慢了。
Mills的建议
大型项目的每一部分由一个团队解决,该队伍以类似外科手术的方式组建,而并非一拥而上。
- 外科医生(首席程序员)。负责定义功能和性能技术说明书,设计程序,编制源代码,测试以及书写技术文档。首席程序员需要极高的天分、丰富的经验和应用数学、业务数据处理或其他方面的大量系统知识和应用知识。
- 副手。他是外科医生的后背,能完成任何一部分工作,但是相对具有的经验较少。他的主要作用是作为设计的思考者、讨论者和评估人员。副手经常在与其他团队讨论有关功能和接口问题时代表自己的小组。他需要详细了解所有的代码,研究设计策略的备选方案。他充当外科医生的保险机制。他甚至可能编制代码,但对代码的任务部分,不承担具体的开发职责。
- 管理员。他是外科医生的老板,必须在人员、薪酬、办公空间等方面具有决定权,但他绝对不能在这些事务上浪费任何时间。他充当与组织中其他管理机构的接口。一个管理员可以为两个团队服务。
- 编辑。外科医生负责文档的生成——出于最大透明度的考虑,他必须创建各种文档,无论是对内部描述还是外部描述。而编辑根据外科医生的草稿或者口述,进行分析和重新组织,提供各种参考信息和书目,对多个版本进行维护,并监督文档生成的机制。
- 两个文秘。管理员和编辑每个人需要一个文秘。管理员的文秘负责并产品文件和使项目协作一致。
- 程序职员。他负责维护编程产品库中所有团队的技术记录。该职员接受文秘性质的培训,承担机器码文件和可读文件的相关管理责任。
- 工具维护人员。承担团队成员所需要的特殊工具的构建、维护和升级责任。工具维护人员常常要开发一些实用程序,编制具有目录的函数库以及宏库。
- 测试人员。测试人员为外科医生的各个功能设计系统提供测试用例,还为外科医生提供日常调试设计测试数据,并负责计划测试的步骤和为单元测试搭建平台。
- 语言专家。他寻找一种简洁、有效的使用语言的方法来解决复杂、晦涩或者棘手的问题。他通常需要对技术进行一些研究(2~3天)。通常一个语言专家可以为2~3个外科医生服务。
以上就是如何参照外科手术队伍对10人的编程团队进行专业化的角色分工。
如何运作
传统的队伍与外科医生团队架构之间区别:
- 传统的队伍将工作进行划分,每人复杂一部分工作的设计与实现;外科手术团队中,外科医生和副手都了解所有的设计和全部的代码。这节省了空间分配、磁盘访问等的劳动量,同时也确保了工作概念上的完整性。
- 在传统的队伍中大家是平等的,出现观点的差异时,不可避免地需要讨论和进行相互的妥协和让步。在外科手术团队中,不存在利益的差别,观点的不一致之处可以由外科医生单方面来统一。

团队的扩建
面对几百人参与的大型任务时,可以按比例来分别。决定设计的人员是原来的1/7或更少。
要有一个系统结构师从上至下进行所有的设计。要使工作易于管理,必须清晰地划分体系结构设计和实现之间的界线。
第4章 贵族专制、民主政治和系统设计
贵族专制统治和民主政治
概念的完整性要求设计必须由一个人,或者非常少数互有默契的人员来实现。
对于非常大型的项目,将设计方法、体系结构方面的工作与具体实现相分离是获得概念完整性的强有力方法。
系统的体系结构指的是完整和详细的用户接口说明。系统的结构师,如同建筑的结构师一样,是用户的代言人。结构师的工作,是运用专业技术知识来支持用户的真正利益,而不是维护销售人员、制作者所鼓吹的利益。
在系统设计中,贵族专制并不会导致创造性降低,实际上创造性活动会因为规范化而得到增强。
在等待时,实现人员应该做什么
整个创造性活动包含了三个独立的阶段:体系结构(architecture)、设计实现(implementation)和物理实现(realization)。在实际情况中,它们往往可以同时开始和并发地进行。
- 体系结构(Conceptualization):这是项目的初步阶段,主要涉及到对系统的高层次思考和构想。在这个阶段,关键是确定系统的主要组成部分,以及这些部分之间的关系和交互方式。体系结构需要对整个系统有一个全局和整体的视角,确保各部分协同工作以满足项目的目标。
- 设计实现(Specification):一旦有了清晰的体系结构,接下来的步骤是进行详细设计。这意味着为系统的每一个部分制定明确、详细的规格。这可以包括数据结构、算法、接口定义、错误处理等。在这个阶段,具体的技术和工具选择也会变得更加明确。
- 物理实现(Construction):这是实际的编码和构建阶段。在此阶段,开发者将之前的设计规格转化为实际的代码,并构建系统。除了编写代码,这个阶段还包括测试、调试和优化以确保系统的正确性和性能。
概念的完整性要求系统只反映唯一的设计理念,用户所见的技术说明来自少数人的思想。恰恰相反,整个系统将会开发得更快,所需要的测试时间将更少。
第5章 画蛇添足
结构师的交互准则和机制
- 牢记是开发人员承担创造性和发明性的实现责任,所以结构师只能建议,而不能支配;
- 时刻准备着为所指定的说明建议一种实现的方法,同样准备接受其他任务能到达目标的方法;
- 对上述的建议保持低调和不公开;
- 准备放弃坚持所做的改进建议。
第6章 贯彻执行
形式化定义和记叙性定义是指定义某个概念、术语或系统时所采用的两种不同方法,它们在目的、结构和应用领域上有所不同。
形式化定义(Formal Definition):
本质:形式化定义是使用精确、严格的语言(通常是数学或逻辑语言)来定义概念。这种定义的目的是去除任何歧义,确保概念的精确性和一致性。
结构:通常包括符号、公式和严格的逻辑构造。
应用:广泛用于数学、计算机科学、逻辑学和其他精确科学领域。例如,在计算机科学中,算法的形式化定义涉及使用伪代码或特定的算法语言来精确描述其步骤和操作。
优点:减少或消除歧义,使得概念、算法或过程可以被精确地理解和分析。
记叙性定义(Narrative Definition):
本质:记叙性定义使用更加通俗、易懂的语言来描述一个概念或术语。它侧重于可读性和易于理解,可能牺牲一些精确性。
结构:通常是自然语言的段落,可能包含比喻、例子或非技术性的解释。
应用:适用于更广泛的听众,特别是对专业术语不太熟悉的人。例如,在向非技术人员解释计算机程序时,可能会采用记叙性定义来描述程序的功能和用途,而不是其具体的编码细节。
优点:更易于普通人理解,适合教育和普及目的。
区别:
- 精确性:形式化定义更精确,适用于专业和科学研究;记叙性定义更侧重于可理解性和通俗性。
- 语言:形式化定义使用严格的、定义良好的技术语言;记叙性定义使用更自由、更接近日常用语的自然语言。
- 目的:形式化定义旨在提供严密、无歧义的说明,适用于专业领域;记叙性定义更侧重于概念的普及和教育,适合非专业听众。
- 应用领域:形式化定义常用于数学、逻辑和计算机科学;记叙性定义则广泛应用于教育、普及科学和日常沟通。
第7章 为什么巴比伦塔会失败
巴比伦塔的管理教训
巴比伦塔失败的原因有两个:
- 缺乏交流
- 缺乏交流的结果—组织
大型编程项目中的交流
团队可以通过如下方式进行交流沟通:
- 非正式途径。
- 会议。
- 工作手册。
项目工作手册
项目工作手册是对项目必须产出的一系列文档进行组组的一种结构。项目所有的文档都必须是该结构的一部分。
事先将项目工作手册设计好,能保证文档的结构本身是规范的,而不是杂乱无章的。有了文档结构,后来书写的文字就可以放置在合适的章节中。
第8章 胸有成竹
生产率会根据任务本身复杂度和困难程度表现出显著差异。
- 对常用编程语句而言,生产率似乎是固定的。这个固定的生产率包括了编程中需要注释并可能存在错误的情况;
- 使用适当的高级语言,编程的生产率可以提高5倍。
第9章 削足适履
规模本身不是坏事,但不必要的规模是不可取的。
培养开发人员从系统整体出发、面向用户的态度是软件编程管理人员最重要的职能。
对于给定的功能,空间越多,速度越快。
数据的表现形式是编程的根本。
第10章 提纲掣领
计算机产品的文档
关键文档:
- 目标
- 技术说慢
- 进度
- 预算
- 组织结构图
- 工作空间的分配
- 报价、预测、价格
软件项目的文档
- 内容:目标
- 内容:产品技术说明
- 时间:进度
- 资金:预算
- 地点:工作空间分配
- 人员:组织图
第11章 未雨绸缪
变更的阶段化是一种必要的技术。每个产品都应该有数字版本号,每个版本都应该有自己的日程表和冻结日期。
管理人员需要参与技术课程,高级技术人才需要进行管理培训。项目目标、进展和管理问题必须在高级人员整体中得到共享。
对于一个广泛使用的程序,其维护总成本通常是开发成本的40%或更多。该成本受用户数目的影响很大。用户越多,所发现的错误也就越多。
程序维护中的一个基本问题是——缺陷修复总会以固定(20%~50%)的几率引入新的bug。所以,整个过程是前进两步,后退一步。
系统软件开发是减少混乱度(减少熵)的过程,所以它本身是处于亚稳态的。软件维护是提高混乱度(增加熵)的过程,即使是最熟练的软件维护工作,也只是放缓了系统退化到非稳态的进程。
第12章 干将莫邪
开发和维护公共的通用编程工具的效率更高。
作者建议每个团队配备一名工具管理人员。这个角色管理所有通用工具,能指导他的客户和老板如何使用工具。同时,他还能编制老板需要的专业工具。
第13章 整体部分
主要内容包括:
- 构建和维护概念完整性:布鲁克斯强调,软件项目的成功很大程度上依赖于保持设计的概念完整性。这意味着软件的核心设计应该由一个或极少数人来掌控,以确保系统的一致性和协调性。
- 分解和分工:他讨论了如何将大型软件项目分解为更小、更易管理的部分,并且指出了合理分工的重要性。布鲁克斯提倡将任务分配给团队成员,同时保持对项目整体的集中控制。
- 通信与协调:在软件项目中,团队成员间的有效通信和协调至关重要。布鲁克斯分析了通信过程中可能出现的障碍,并提出了解决这些难题的方法。
- 模块化设计:章节中还强调了模块化设计的重要性。模块化可以帮助管理复杂性,允许团队成员独立工作于项目的不同部分,同时又能保持整体的协调一致。
- 质量保证:讨论了在整个开发过程中维持软件质量的重要性,包括代码审查、测试和文档化。
第14章 祸起萧墙
- 沟通不足导致的问题:
- 问题:项目进度受阻,主要由于团队内部沟通不足。
- 原因:信息在团队成员间传递不畅,缺乏有效的沟通机制。
- 解决方法:实施定期会议和明确的项目文档来提高沟通效率,确保信息流通。
- 项目进度追踪的挑战:
- 问题:难以准确评估和追踪项目进度。
- 原因:缺乏清晰的里程碑和有效的进度监控机制。
- 解决方法:设立明确的项目里程碑,并使用进度追踪工具来持续监控各阶段。
- 风险评估的不足:
- 问题:未能及时识别和管理项目风险。
- 原因:项目初期未进行彻底的风险评估。
- 解决方法:在项目开始阶段进行详尽的风险评估,并制定含备选方案的风险缓解计划。
- 决策不透明造成的问题:
- 问题:团队成员对项目方向和决策感到不确定。
- 原因:管理层决策过程缺乏透明度。
- 解决方法:确保管理层的决策对团队成员透明,并在关键决策中包含团队的意见。
- 团队协作不足:
- 问题:团队成员间缺乏协作和理解。
- 原因:缺少促进团队协作和交流的机制。
- 解决方法:通过团队建设活动和跨部门交流提升团队协作和相互理解。
- 项目审查的重要性:
- 问题:项目中出现的问题未能及时被识别和解决。
- 原因:缺乏定期的项目审查机制。
- 解决方法:实施定期项目审查,评估进度,识别问题,并根据审查结果调整计划。
- 复杂项目管理的挑战:
- 问题:大型复杂项目难以管理。
- 原因:项目设计缺乏模块化和分解。
- 解决方法:采用模块化设计,并将大型项目分解为小型、可管理的部分。
- 领导和责任的不明确:
- 问题:项目中缺乏明确的领导和责任感。
- 原因:未明确团队成员的角色和责任。
- 解决方法:明确项目领导层级和责任分配,强化团队成员的责任感。
第15章 另外一面
- 文档的重要性:当程序使用者与程序作者在时间或空间上有较大距离时,文档变得尤为重要。良好的文档能够减少使用者对程序不清晰的困扰。
- 教授新手文档编写:向新程序员展示如何编写文档,而不仅仅是告诉他们去做。这是因为他们可能不知道如何有效地进行,而错误地被认为是缺乏兴趣或纪律。
- 文档的结构:文档应该从概述开始,如程序的目的、输入和输出。许多文档在概述方面做得不够。
- 针对不同用户的文档:有三种类型的用户——偶尔使用的用户、依赖程序的用户和将修改程序的人。他们需要不同的文档。
- 依赖程序的用户需求:这类用户需要能够运行的测试案例来验证程序是否正常工作。
- 测试边界条件:测试最高有效值和稍高的无效值,以确保适当的错误处理。
- 流程图的有效性:提供高层次概述或描述高层次步骤的单页流程图非常有用。详细到多页的流程图则不太实用。
- 文档与代码的整合:在源代码外部维护文档会导致其与代码不同步。建议将文档融入代码中。
- 编码实践的负担:有些编程实践带来了过重的负担,我们的前辈们无法承受,我们也一样。需要减少这种负担。
- 文档的附加:将文档附加到本来就需要存在的程序元素(如函数)上。
- 代码的记录问题:代码经常记录不足。而严格的编码标准导致人们过度记录不重要的事物。
- 文档的重要性和时机:即使如此,重要的部分(如高层次概述)通常记录不足。应该在编码的同时记录文档,而不是之后。
- 以人为本的原则:机器是为人服务的,而不是人为了服务机器。
第16章 没有银弹-软件工程中的根本和次要问题
本章讨论了软件工程的复杂性,并论证了在十年内没有任何技术或管理发展能带来生产力、可靠性或简易性的十倍提升。布鲁克斯讨论了次要复杂性和根本复杂性之间的区别,强调了软件工程独有的挑战,这些挑战不容易通过单一的突破或解决方案解决。他建议专注于逐步改进和利用现有技术和方法来解决“次要”困难,同时认识到软件的“根本”复杂性需要更细致和持续的方法来应对。
- 避免重构现有解决方案:强调利用现有解决方案而不是从头开始构建的重要性。
- 快速原型制作:提倡使用快速原型制作更有效地确定软件要求。
- 系统的有机增长:建议有机地增长系统,在增加大量功能之前,先全面测试基本功能。
- 没有神奇解决方案:断言没有单一解决方案或“银弹”能显著简化软件开发过程。
- 软件工程中的困难类型:
- 根本:软件性质固有的困难。
- 次要:当前与软件生产相关的非固有困难。
- 概念错误的重要性:强调概念错误比语法错误更重要。
- 软件的根本问题:
- 复杂性:优秀的软件不应有重复元素。
- 符合性:软件应与现实世界的接口相符合。
- 可变性:软件需要适应不断变化的需求。
- 隐形/无形:由于软件的非物质性质带来的挑战。
- 软件扩展的问题:扩展软件不等同于简单地添加现有组件的重复。
- 已解决的次要困难:
- 高级语言。
- 分时系统。
- 统一的编程环境和IDE。
- 提出的解决方案及其局限性:
- 如Ada等高级语言。
- 面向对象编程和抽象数据类型。
- 人工智能和自动编程。
- 图形编程和程序验证。
- 增强的环境和工具。
- 先进的工作站。
- 生产率公式:涉及任务中组件的频率和所需时间的公式。
- 有前景的方法:
- 购买组件而不是从头创建。
- 精炼需求和快速原型制作。
- 增量开发和“培养”软件。
- 强调优秀设计和设计师的重要性,而不仅仅是管理
第17章 再论“没有银弹”
在后续的“No Silver Bullet — Refired”中,布鲁克斯重新审视了他的这些观点,并讨论了自“No Silver Bullet”发表以来的软件工程发展。他回顾了一些可能被认为是提高生产力的技术或方法,如面向对象编程、人工智能、图形界面等,并评估了这些技术或方法对软件开发困难的实际影响。布鲁克斯进一步强调,虽然这些技术带来了进步,但它们并没有根本解决软件开发的固有复杂性。他继续主张,提高软件工程的关键在于更好的管理、更好的技术训练、以及开发合适的工具和环境。