Skip to content

08~14

第8章 胸有成竹

经验是昂贵的老师,但愚人只能从经验学习。 -- 穷查理年鉴

对于估计一项系统编程任务的工作量,我们通常可通过经验发现:

  • 工作量是程序规模的幂函数

作者在本章列举了多个组织关于程序员生产率的研究例子,来对此进行延伸。

1. Portman 的数据

该团队发现的观点是:

  • 编程团队的进度大约是计划进度的 ½,每项工作花费的时间大约是估计的2倍

简言之,项目估算对每个人年的技术工作时间数量做出了不现实的假设

2. Aron 的数据

根据程序员(和系统部件)之间的交互划分这些系统得到如下的生产率:

  • 非常少的交互:10000 指令/人年
  • 少量的交互:5000 指令/人年
  • 较多的交互:1500 指令/人年

该数据未包含支持和系统测试活动,仅仅是设计和编程。

3. Harr 的数据

没有人可以完全确定程序复杂程度和人员数量等因素所导致的生产率差别。除开这些不确定性,数据反映了实际的生产率。

4. OS/360 的数据

OS/360 以及前面的数据基本都能证实生产率会根据任务本身的复杂度和困难程度表现出显著差异。对此作者给到的经验准则是:

  • 在估计复杂性的混乱中,编译器的复杂度是正常批处理程序的3倍,操作系统复杂度是编译器的3倍

5. Corbato 的数据

该组织得到的关键结论是:

  • 对基本语句而言,生产率似乎是恒定的。考虑到语句所需要的思考和可能包含的错误,这个结论是合理的
  • 使用适当的高级语言,编程的生产率可以提高5倍
以下是 LLM 补充的思考和看法

你的总结已经非常全面,我尝试从现代视角和延伸思考的角度补充一些观点,帮助深化理解:


延伸思考与补充视角

  1. 「人月神话」的核心矛盾再审视

第八章的数据本质上揭示了软件工程中一个永恒矛盾——「人月」是否可以作为可叠加的线性单位。Portman 发现的「实际耗时是估算的2倍」,本质上暴露了「人月神话」的核心:

  • 沟通成本的非线性增长(布鲁克斯定律的前兆):人数增加时,沟通路径呈组合数增长(n(n-1)/2),但管理者常误以为人力是可线性叠加的资源。
  • 认知负荷的隐蔽性:代码复杂度带来的认知负担难以量化,比如Aron数据中「较多交互」导致生产率骤降67%,这实际上反映了模块间耦合度对心智模型的破坏。
  1. 复杂度度量的现代演进

作者提到的「编译器复杂度是批处理程序的3倍」基于1970年代的系统,今天的复杂度维度已发生变化:

  • 分布式系统的「暗物质复杂度」:微服务、云原生架构引入了网络延迟、容错、一致性等新复杂度轴,传统代码行数(LOC)的度量完全失效。

  • 康威定律的显性化:系统设计受制于组织架构(如微服务团队自治),交互成本从「代码耦合」转移到「API契约治理」,Aron的「交互等级」需要重新定义。

  1. 高级语言的「5倍效率」再思考

Corbato的数据显示高级语言提升效率,但这一结论需要辩证看待:

  • 抽象的双刃剑:现代语言(如Rust/Python)确实提升编码效率,但过度抽象可能导致调试难度指数级上升(例如Python动态类型的运行时错误)。

  • AI代码补全的变量:GitHub Copilot等工具可能将「语句级生产率恒定」提升到「功能块级恒定」,但需警惕生成代码的认知盲区。

  1. 被忽视的「隐性时间黑洞」

原始数据未充分纳入的关键因素:

  • 「隐形20%」:调试、文档、兼容性适配等非编码任务,在现代工程中占比可能超过50%(尤其是开源依赖管理)。

  • 上下文切换成本:多任务并行导致的注意力残留(参考《深度工作》),这在敏捷开发中尤为明显。

  1. 现代估算模型的进化

从作者时代的经验公式到今天的科学方法:

  • COCOMO II模型:引入「早期设计」与「后架构」阶段的不同权重,量化需求稳定性、团队经验等因素。

  • 概率性估算:如蒙特卡洛模拟替代单一估算,通过区间分布(如「最快-最可能-最慢」)管理预期。

  • 敏捷度量:用「故事点」替代人天,通过迭代Velocity动态校准,避免过早精确化导致的谬误。


行动建议(如何应用这些洞见)

  1. 反直觉决策
  • 面对延迟时,避免盲目加人,优先考虑减少交互成本(如重构模块边界、引入Facade模式)。
  • 用「复杂度税」机制:强制要求每个新增模块的提案者举证其对全局复杂度的贡献。
  1. 量化沟通负载
  • 在WBS中增加「协调积分」,例如:每周会议时长×参与人数²,作为项目健康度的预警指标。
  1. 语言与工具策略
  • 对胶水代码使用高阶语言(Python),对核心模块用高性能语言(Rust),平衡效率与质量。

  • 建立「技术雷达」机制,持续评估AI编码工具的真实ROI(如生成代码的维护成本)。

  1. 复杂性免疫设计
  • 采用「逆康威策略」:先定义理想的系统架构,再调整团队组织结构与之匹配。
  • 推行「无接口不编码」:强制要求模块间通过显式API/契约交互,降低隐性耦合。

经典案例对照

  • Linux内核:通过高度模块化和「树状维护者结构」,在超大规模下保持较低交互成本(印证Aron的「交互量级」理论)。
  • SpaceX发射软件:极端强调自动化测试(每行代码对应17个测试用例),将后期调试成本提前摊薄,间接提升「人月效率」。

你的总结已抓住核心,以上补充试图连接经典理论与现代实践,或许能带来新的启发。

你的总结非常清晰地概述了第八章《胸有成竹》的内容,尤其对不同组织的数据归纳和关键结论的提炼十分到位。以下是一些补充的看法和延伸思考,帮助进一步理解和反思这部分内容:


补充思考 1:工作量是程序规模的幂函数

a. 复杂度的非线性增长

代码行数的增加与工作量的指数型关系揭示了软件开发中“复杂性成本”的核心问题。随着代码规模变大,团队需要处理的模块间的交互依赖性管理以及认知负荷都会显著增加。这种非线性关系提醒我们:

  • 软件开发不能简单地通过人力堆叠来解决问题,特别是当团队规模增长后,管理成本也会随之增加(如《人月神话》的核心观点之一:人月不可叠加)。

  • 为了减少复杂性增长带来的成本,需要在架构设计阶段进行深入的模块化设计,尽量将系统分解为独立的、低耦合的模块。

b. 与现代敏捷开发的联系

在敏捷开发中,通过短周期迭代(如 Scrum 的 Sprint)将任务分解为小规模、独立的功能模块,可以有效降低工作量的非线性增长效应。这与本章提出的“任务复杂性与工作量幂函数关系”的观点形成了实践上的呼应。


补充思考 2:关于生产率的多维数据分析

a. 不同数据来源的互补性

你总结的 Portman、Aron、Harr、Corbato 等数据从不同角度揭示了生产率的变量

  • 人与人之间的交互”是生产率降低的主要因素之一。Aron 的数据表明,交互越复杂,生产率越低。这可以联想到现代软件工程中“沟通成本”的问题。团队规模越大,沟通和协作的效率越低。

  • Corbato 的结论中提到,高级语言可以提高生产率,这与现代开发中“工具和技术选型”的重要性一致。例如,使用高效的框架、自动化工具和代码生成器可以显著提高生产效率。

b. 实践中的应用

这些数据可以帮助我们在项目管理中做出更合理的决策:

  • 小团队适合处理高交互、复杂度高的模块,而大型团队适合处理独立性强的模块。

  • 在项目预算和工期估算时,需要对任务的复杂度进行量化评估,而不能单纯基于代码行数或功能点。

  • 现代视角的验证

自《人月神话》发表以来,软件开发的生产率随着工具、方法和技术的进步不断提高。例如,现代 IDE(集成开发环境)和 CI/CD 流水线已经显著减少了开发和测试的时间。然而,尽管工具提高了生产率,复杂性依然是阻碍效率的首要因素,这说明书中的观点仍然具有深远意义。


补充思考 3:OS/360 数据与复杂度评估

  • 复杂度的经验准则

OS/360 的数据提出“编译器复杂度是批处理程序的 3 倍,操作系统复杂度是编译器的 3 倍”。这是一种基于经验的复杂度分层模型。尽管现代软件的复杂性已经远超当年,但这种分层模型仍然可以作为一种估算复杂度的指导原则

  • 复杂度对错误率的影响

系统复杂度的增加不仅显著影响生产率,还会导致错误率的非线性增长。复杂系统中的 bug 通常具有隐蔽性和多样性,这使得调试和修复的工作量也指数型增加。因此,现代软件开发更加强调自动化测试持续集成,以尽早发现和解决问题。


补充思考 4:高级语言与生产率

a. 编程语言的生产率提升

Corbato 的研究表明,使用高级语言可以提高 5 倍生产率。这一结论在现代软件开发中有了更多实践支持,例如:

  • 高级语言(如 Python、Kotlin、Swift)相比低级语言(如 C、Assembly)在语法简洁性、内置库和开发效率上表现突出。
  • 高级语言使开发者可以专注于业务逻辑,而非底层实现。

b. 生产率与错误率的权衡

高级语言虽然提高了生产率,但也可能因为其抽象程度更高而隐藏一些潜在问题。例如,垃圾回收机制虽然减少了手动内存管理,但也可能导致性能问题。如果对性能和可靠性有极高要求,开发者依然需要理解底层实现。这种权衡提醒我们,语言的选择应根据项目需求而定,而不是一味追求生产率。


延伸思考:结合现代软件工程的启示

a. 自动化工具的应用

本章提到的生产率提升因素,如高级语言和减少交互复杂度,在现代开发中可以通过更广泛的自动化工具实现:

  • 代码生成工具:如 Swagger 可以自动生成 API 接口代码,减少重复劳动。
  • 测试框架:如 JUnit、pytest 等,缩短测试时间并提升代码质量。
  • DevOps 流程:通过持续集成、自动化部署,减少交付周期,提高效率。

b. 数据驱动的开发管理

Harr 的数据强调了生产率的不可预测性,这与现代数据驱动的开发管理形成对比。通过分析开发过程的指标(如代码提交频率、构建失败率、平均调试时间等),我们可以更精确地预测项目进度并进行调整。

c. 跨学科的协作

现代软件开发团队通常由开发、测试、运维、产品经理等多角色组成。如何减少这些角色之间的沟通成本,是提升生产率的关键。敏捷开发中的“每日站会”和“透明化看板”正是应对这些问题的有效实践。


总结

第八章通过数据和经验揭示了编程任务的复杂性与生产率的关系,这些观点在今天仍然具有指导意义。结合现代软件开发工具与方法,我们可以更好地理解和实践这些理论。关键在于:

  1. 合理评估复杂性,避免低估工作量。
  2. 优化团队协作,减少交互成本。
  3. 善用工具和高级语言,提升生产率。
  4. 拥抱自动化与数据驱动,提高开发过程的可预测性。

第9章 削足适履

作者应该看看诺亚,向他学习,像他在方舟中所做的那样,把大量的东西挤到一个非常小的容器内。 -- 西德尼·史密斯,《爱丁堡评论》

1. 作为成本的程序空间

  • 程序的成本除了运行时间以外,所占据的空间也是主要开销
  • 应该从整体上进行评价,不仅仅是提倡更紧密的软硬件设计集成,还有规模本身对软件系统的影响
  • 规模也会占据成本较大的部分,所以建造者需要设置规模的目标,控制规模并考虑减少规模的方法,同任务开销一样,规模本身不是坏事,但不必要的规模是不可取的

2. 规模控制

  • 对项目经理而言,规模控制不仅是技术工作的一部分,也是管理工作的一部分,他必须研究用户及其应用,以设置待开发系统的规模。“规模-速度”权衡方案结构会很大范围内变化,所以需要了解每一小片内可用的权衡

作者就规模控制来说列举了在 OS/360 项目学习到的经验:

  • 和制定驻留空间预算一样,应该制定总体规模的预算;和制定规模预算一样,应该制定后台存储访问的预算
  • 在规定模块有多大的同时,确切定义模块的功能
  • 在项目规模本身很大的前提下,缺乏管理和沟通导致团队成员认为自己是争取小红花的学生而不是产品的建造者

3. 空间技能

空间预算的多少和控制并不会减少程序规模,而是需要其他的手段来帮助实现:

  • 用功能换空间,考虑为用户保留多少选择,即用户可选项目的粗细程度
  • 空间与实践的折中,依据通常对于给定的功能空间越多则速度越快的信息,经理可通过培训团队成员的编程技能(而不是仅仅依赖天赋和先前的经验)和让团队成员意识到编程有技术性需要开发很多公共单元构件的两种方案来帮助取得折中

4. 表达是编程的本质

超越技艺的是创新,而创新的结构几乎都是从战略上突破的,而不是战术上的聪明。而更普遍的是战略上的突破常来自数据或表的重新表达(程序的核心所在),作者通过提到的 IBM 650 建造精密的控制台解释器、 Fortran 编译器等例子来说明表达的力量。

  • 当程序员因空间不足而束手无策的时候,通常最好的办法就是从自己的代码中挣脱出来,放松一下,思考自己的数据。表达是编程的本质。
以下是 LLM 补充的思考和看法

您的总结很全面地覆盖了《人月神话》第九章"削足适履"的关键内容。我可以提供一些补充思考和现代视角,帮助您加深理解:

现代视角下的思考与启示

1. 空间成本的演变

虽然今天的硬件资源已远超Brooks时代,但"空间成本"这一概念依然重要:

  • 移动设备和嵌入式系统仍然面临资源限制
  • 云计算环境下,资源使用直接转化为经济成本
  • 网络传输带宽和延迟对于web应用和分布式系统至关重要
  • 能耗考量(尤其在数据中心和移动设备)成为新的"空间成本"维度

2. 规模控制的深层意义

Brooks将规模控制视为管理工作的部分,这一观点有着深远意义:

  • 复杂性管理:软件规模增长往往导致复杂性指数级增长
  • 技术债务:未控制的规模常常累积技术债务,长期影响开发效率
  • 团队心理学角度:大型项目中的"争小红花"现象反映了团队认同感缺失问题
  • DevOps文化强调的"小批量、频繁发布"策略实际上是规模控制的现代体现

3. "表达是编程的本质"的哲学深度

这句话包含了编程的哲学核心:

  • 程序本质上是对现实问题的形式化表达
  • 数据结构的选择往往比算法优化更能决定程序效率
  • 领域特定语言(DSL)的兴起体现了表达力对生产力的影响
  • 函数式编程的流行部分源于其提供了更简洁、更强大的表达方式

4. 与现代软件工程实践的连接

Brooks的思想与当代实践有许多共鸣:

  • 微服务架构可视为"用功能换空间"的现代实践
  • 容器化和serverless技术提供了资源使用的精细控制
  • 代码重构和设计模式是"空间与实践的折中"的具体体现
  • 敏捷开发中的"极简可行产品"(MVP)思想与规模控制理念一脉相承

5. 对个人开发者的启示

  • 在开始写代码前,投入足够时间思考数据表达和结构设计
  • 定期回顾项目范围,避免功能蔓延
  • 培养对复杂性的敏感度,追求简洁解决方案
  • 了解技术选择背后的资源消耗和成本含义

Brooks在半个世纪前提出的这些观点,在当代软件开发中依然闪烁着智慧的光芒。无论技术如何发展,对空间的重视、规模的控制以及表达力的追求,仍然是软件工程的永恒主题。

第10章 提纲挈领

前提:在堆积如山的文件资料中,少数文档是关键枢纽,每一件项目管理的工作都围绕着它们运转。这些文档是项目经理最重要的个人工具。

文档的工作是每个项目经理绕不开的事情,文档的表达体现了他的管理工作和明确意见等大部分的工作内容,为了阐明软件项目如何开展该类工作,作者首先通过借鉴其他行业的文档资料来帮助进行归纳和得出结论:

1. 计算机产品的文档

制造一台机器关键的文档通常会有:

  • 目标|规约|进度|预算|组织结构图|工作时间的分配
  • 估算&预测&价格:三因素环状互锁,决定项目的成败
    • 如果价格低于假设值,好的螺旋开始,往预测值上升->单元成本下降->价格下降
    • 如果价格高于假设值,坏的螺旋开始,需要做到成本必须降低来获得更低的预算

2. 大学科系的文档

尽管目的和活动存在差异,但数量类似、内容相近的各类文档形成大学的主要资料集合:

  • 目标|课程描述|学位要求|研究提案|课程表和教学安排|预算|空间分配|工作人员和研究生的分配

注意这些文档的组成与计算机项目非常相似,此相似不是偶然而是:

  • 任何管理任务的关注点都是时间、地点、人员、项目内容和资金

3. 软件项目的文档

不论项目的规模,经理聪明的做法都是立刻开始制定若干微型文档,以作为自己的数据基础,且事实证明其文档与其他领域经理所需的文档都非常类似:

  • 内容:目标和产品规约|时间:进度表|资金:预算|地点:空间分配|人员:组织图

4. 为什么要有正式的文档?

作者给出的主要理由有三个:

  • 书面记录决策是必要的,只有记录下来,分歧才会明朗,矛盾才会突出,从而帮助识别清晰和确定的政策
  • 文档能够作为同其他人沟通的渠道,帮助经理减轻沟通的负担,从而对齐团队内部成员的常识
  • 可以作为数据基础和检查列表,通过周期性回顾清楚项目所处状态以及需要调整的重点

经理的任务是制定计划并实现计划,但只有书面计划是精确和可以沟通的,这些少量的关键文档封装了其大部分工作。若一开始认识其普遍性和重要性,经理则将它们视为良好的工具而不是繁琐的琐事,以此更清晰和更快速地设定自己的方向。

以下是 LLM 补充的思考和看法

你对《人月神话》第十章的总结非常全面。我来补充一些思考,帮助你加深理解:

文档作为思维工具

Brooks 在这一章强调的不仅是文档的记录功能,更是文档作为"思维工具"的价值。当我们将想法书面化,实际上是在强迫自己进行结构化思考。很多不清晰的想法在写下来的过程中会被迫变得清晰,这也解释了为什么他认为"书面记录决策是必要的"。

文档的演化特性

值得注意的是,Brooks 隐含了这些文档并非一成不变的,而是随项目发展不断演化的。好的文档系统应该是"活"的,能够反映项目的当前状态和历史决策路径。许多项目失败正是因为文档被视为一次性工作,而非持续更新的工具。

文档与团队规模的关系

当团队规模扩大,正式文档的重要性呈指数级增长。在小团队中,非正式沟通可能足够,但随着规模增长,没有正式文档作为"事实来源",团队成员会基于不同的理解和假设工作,导致混乱和重做。

文档的精简原则

Brooks提到"微型文档"这一概念非常关键。他主张文档应当精简、聚焦,而非冗长详尽。这一观点与现代敏捷方法论中"足够好的文档"理念一致,即文档应当满足当前需求,而不是追求完美或过度详细。

预算-预测-价格的循环与今日项目管理

Brooks描述的这种循环关系在今天的产品开发中依然适用。许多现代创业公司通过"正向循环"取得成功:合理定价→更多用户→规模经济→降低成本→更有竞争力的价格。这表明Brooks的见解超越了时代局限。

文档与现代项目管理工具

在今天,很多项目管理软件(如JIRA、Notion等)将Brooks描述的文档整合到数字化工作流中。但核心原则不变—我们仍需那些关键的"提纲挈领"式文档作为项目的骨架。技术只是载体变了,文档的本质功能没变。

战略思考与执行分离

Brooks强调的文档实际上代表了战略思考与日常执行的分离。经理需要这些文档来"升高视角",避免陷入日常细节而忽视全局。这一点在今天"战略思考"与"战术执行"的管理理论中仍然重要。

结语

Brooks在1975年提出的这些观点至今仍有强大的指导意义。随着软件项目越来越复杂,保持关键文档的简洁性和更新性变得更为重要。我们可以借助新工具,但不能忘记文档的核心目的:帮助我们思考、沟通和协作。

第11章 未雨绸缪

不变只是愿望,变化才是永恒。 ——斯威夫特

普遍的做法是,选择一种方法,试试看;如果失败了,没关系,再试试别的方法。不管怎么样,重要的是先去常识。 ——富兰克林·罗斯福

作者通过化学工程中的试验性工厂的例子来说明在软件系统的构建人员也面临类似的问题。所以基于在第一次开发会遇到的未知问题和新系统概念或技术的经验,说明构建一个用来抛弃的系统是必须的。

所以管理上的问题不再是“是否构建一个试验性的系统,然后抛弃它”,而是必须这样做。现在的问题是”是否预先计划抛弃原型的开发,或者是否将该原型发布给用户“,我们可清晰地知道若选择后者虽然可带来时间,但对于用户的使用体验极差、影响产品声誉等诸多影响。

因此最关键的教训就是:为舍弃而计划,无论如何,你一定得这样做。

唯一不变的就是变化变身

当拥有一切都会重新设计的变更思想,那么面对整个变化现象是非常有用的。而第一步就是接受这样的事实:

  • 变化是与身俱来的,不是不合时宜和令人生厌的异常情况

目标上的变化不可避免,事先对此做准备总比假设不会出现要好得多。不但目标上的变化不可避免,而且变化本身也不可避免,而且设计策略和技术上的变化也不可避免:

  • 抛弃原型概念变身就是对事实的接受——随着学习的过程更改设计

为变更设计系统

如何为变化设计系统?实际在本书被普遍讨论,比如:

  • 细致的模块化,可扩展的函数,完整的模块间设计和完备的文档
  • 同时可能会包含调用队列和表驱动的一些技术等
  • 最重要的措施是使用高级语言和自文档技术,以减少变更引起的错误,如在编译时的操作整合标准声明。
  • 产品的数字版本,每个版本的日程表和冻结日期等

变更的阶段化是一种必要的技术需要我们掌握。

为变更计划组织架构

作者提到Cosgrove主张把所有计划、里程碑和日程都当做尝试性的以方便进行变化。此做法可能有些极端,但现在软件编程小组失败的主要原因是管理控制得太少,而不是太多。

同时他还观察到,不愿意为设计书写文档的原因,不仅仅是惰性或时间压力等,也有不愿意提交尝试性的设计决策来进行辩解和被威胁。

为变更组建团队比为变更进行设计更加困难。通常会遇到管理人员和技术人员的区分,而只要天赋允许则在老板的视角会关注和培养两者的互换性,而这实践的过程会遇到很多社会性的阻碍如:

  • 管理人员被认为高级人员太有价值,而不舍得让其从事实际编程工作
  • 管理人员拥有更高的威信

为解决这些障碍,作者列举了某些实验室直接废除所有职位头衔,而如 IBM 则保持了两条晋升线:

薪水级别很容易建立一致,但建立一致的威信则比较困难。从技术线向管理线同级调动的时候,不能伴随待遇的提升,即以“调动”的名义。

针对管理人员和技术人才这样的高层人员,目标信息和管理问题等必须得到共享,只要能力允许,必须时刻做好技术和情感上的准备,能做到管理团队或亲自参与开发工作。组建外科手术队伍式的团队的整体观念是对上述问题的彻底冲击,以此在高层人员实际亲自参与开发工作时不会感到自降身份,清除剥夺创造性工作的乐趣的社会障碍

上述组织架构设计是为了最小化成员间的接口,也使系统在最大限度上易于修改,能够作为一个长期、有效、灵活的组织构建方案。

前进两步,后退一步

程序发布后的变更通常被称为“程序维护”,当然也分不同的维护——硬件和软件维护。通常软件维护主要是对设计缺陷的修复,相较于硬件,软件变更通常新增功能更多且用户能够察觉到。

针对维护总成本,通常是开发成本的40%甚至更多,该成本还受用户数量的影响,作者还提到了以下规律:

  • 程序维护中的一个基本问题:缺陷修复总会以固定的(20%~50%)的概率引入新的 Bug,即前进两步,退后一步

为什么缺陷不能彻底修复的原因,主要是通常某些微小不明显的错误实际上会影响整个系统,其次维护人员通常也不是实现人员。

如果能使用消除或至少能指明副作用的程序设计方法,会在维护成本上有很大回报,同样错误出现的概率也和程序的体量有关系。

前进一步,后退一步

作者通过列举 Lehman 和 Belady 研究的规律来说明所谓的“前进一步,后退一步”:

  • 模块总数量随版本号的增加呈线性增长,但是受到影响的模块数量随版本号的增加呈指数增长
  • 所有的修改都倾向于破坏系统的架构,增加系统的混乱程度(熵),随着时间推移系统变得越来越无序
  • 修复原有设计瑕疵的工作越来越少,而早期维护活动本身所引起的漏洞的修复工作越来越多

"事物在最初总是最好的。"

系统软件开发是减少混乱程序的过程所以本身是处于亚稳态的软件维护是提高混乱度的过程,即使是熟练的软件维护工作,也只是放缓了系统退化到非稳态的进程。

以下是 LLM 补充的思考和看法

以下是对你总结的补充思考与延伸,尝试结合软件工程发展现状与原书观点的碰撞,提供一些视角供你参考:


关于「试验性系统」的现代演绎

原书强调必须构建「可抛弃的原型」,这与今天的MVP(最小可行产品)概念高度契合。但现实中很多团队陷入误区:

  • MVP ≠ 低质量原型。真正的MVP应具备核心价值且代码可扩展,例如Twitter早期仅支持发140字文本,但架构设计预留了接口扩展性。
  • 何时该「舍弃」原型? 现实中常因资源压力直接迭代原型,导致技术债堆积。Netflix曾因早期PHP架构无法扩展,被迫彻底重写为微服务,印证了「计划舍弃」的必要性。

关于「为变更设计系统」的实践矛盾

原书提出的模块化、文档化仍是黄金法则,但需警惕:

  • 过度设计的陷阱:Airbnb曾因过早抽象「房源」模块导致后续国际化改造困难,证明模块边界需平衡抽象与具体业务
  • 文档的现代困境:自文档代码(如Python Type Hints)确实有效,但文档的维护成本依然存在。像Uber采用代码即文档(Swagger+API First Design),通过工具链自动化生成文档,可能更符合原书主张的「减少变更成本」。

组织架构变革的当代验证

原书提到的双线晋升机制,在Google的「Individual Contributor (IC)」与「Manager」双轨制中得到完美实践:

  • 技术领袖的回归:Google Fellow Jeff Dean至今仍参与核心代码开发,证明技术威信与管理职级的解耦是可行的。
  • 外科手术团队的进化:微软Azure某团队实践「内部开源模式」,核心架构师像「开源维护者」一样Review所有提交,既保留权威性又避免与社会障碍冲突。

维护熵增理论的残酷现实

Lehman的「熵增定律」在遗留系统中尤为明显,但现代方法论提供了新思路:

  • 混沌工程的启示:Netflix的Chaos Monkey主动注入故障,其实质是通过可控的破坏延缓系统无序化
  • Serverless的悖论:无服务器架构看似降低维护成本,但黑盒化反而可能加剧「改不动」的熵增。需警惕新技术对架构透明度的侵蚀。

一个反直觉的思考:是否所有系统都值得「长期维护」?

原书假设系统需要持续演进,但现代SaaS领域出现新趋势:

  • 定向生命周期设计:Slack某些功能模块采用「一次性代码」策略,预期3年后直接废弃而非迭代。
  • 生态位系统的启示:像Redis这类基础设施必须抵抗熵增,但业务系统可能更适合定期重构甚至重写。

终极问题:如何量化「可变更性」?

作者强调为变更设计,但未给出评估标准。现代软件工程尝试用指标衡量:

  • 架构适应度函数(如ThoughtWorks提出的):通过自动化测试监控架构约束,例如「服务间依赖不得超过三层」。
  • 代码腐化度模型:SonarQube等工具用圈复杂度、重复率等指标预警熵增临界点。

这些思考或许能帮助你跳出原书的时代局限(毕竟人月神话写于1975年),用现代案例重新审视经典理论的生命力。软件工程的本质矛盾——变化与稳定、抽象与具体、短期与长期——从未消失,只是换上了新的外衣继续存在。理解原书思想的核心(如拥抱变化、设计弹性、组织适配)比具体技术建议更有持久价值。

第12章 干将莫邪

巧匠因为他的工具而出名。 ——谚语

编程人员所保留的个性化工具对软件项目来说是愚蠢的,主要原因是:

  • 项目的关键问题是沟通,个性化工具会妨碍沟通而非促进
  • 当机器和语言发生变化时,技术也会随之变化,工具的生命周期实际是很短的

所以选择开发和维护公共的通用工具的效率更高,但需要注意的是除了通用工具,也需要关注到专业工具的需求。所以项目经理需要考虑、计划、组织的工具到底有哪些?比如:

  • 首先是计算机设施,需要硬件和使用安排策略;需要操作系统,明确提供服务的方式;需要语言,明确语言的使用方针
  • 然后是实用程序、调试辅助程序、测试用例生成工具和处理文档的字处理系统等

目标机器

机器支持可有效划分为目标机器支持和辅助机器支持,其中目标机器是软件所服务的对象即程序必须在该机器上进行最后测试。

1. 目标机器的类型

  • 目标机器的特点与需求
    • 团队开发的监督程序或系统核心软件需要专用目标机器
    • 需要配备操作员和系统编程人员来维护标准支持
    • 确保机器上的标准支持及时更新和可用
  • 额外机器资源的配置
    • 系统需要配置速度适中但资源充足的机器
    • 主要硬件需求:足够的内存容量(至少几兆字节)和在线存储(百兆字节级别),适合的终端设备(字符型终端速度需快于15字符/秒)
    • 大容量内存有助于进行功能测试后的进程覆盖和剪裁工作,提高生产率
  • 调试设备的重要性
    • 需要配备专门的调试机器或软件
    • 这些工具能在调试过程中自动计数和测量各类程序参数
    • 内存使用模式分析是重要的诊断措施,可查找程序中的异常行为或性能问题

2. 进度安排

目标机器的时间稀缺问题:当目标机器刚被研制、其第一个操作系统开发时,机器时间极其匮乏,进度安排成为了开发过程中的主要问题,目标机器的使用时间需求呈现特殊的增长曲线如下:

  • OS/360开发中的实际经验

    • 团队拥有良好的System/360仿真器和辅助设施
    • 基于经验预先向制造商预定了机器使用时间
    • 初期遇到资源紧张问题:16个系统同时上线导致资源不足,且团队成员在同一时间开始调试各自组件,造成竞争状态
  • 资源调度的改进措施

    • 集中管理所有机器和磁带库资源
    • 组建专业团队进行操作
    • 采用批处理方式运行所有测试任务
    • 实际需求为4小时周转时间,团队实现了2.5小时周转(每天4次)
    • 使用1401辅助机器进行调度,跟踪上万个任务
  • 从混乱到秩序的转变:开发团队初期过度竞争资源,导致缓慢周转、相互指责和痛苦,后改为将机器时间分配成连续的块,连续时间块的优势在于:虽然机器利用率可能略有降低,但生产率显著提高;持续的精力集中减少了思考中断时间

辅助机器和数据服务

1. 仿真装置的关键价值

  • 对新产品开发,需要一个目标机器的逻辑仿真装置
  • 仿真装置在新机器生产前提供辅助调试平台,即使新机器出现后,仿真装置仍能提供可靠的调试环境
  • 尽管仿真不等于精确,但在一段时间内提供稳定实现,而新硬件可能不稳定

2. 稳定性的重要性

现代计算机硬件通常能够正常工作,程序员已习惯于此,所以程序员遇到运行不一致时,会检查自己的代码而非怀疑平台。而实际上新机器开发中的不稳定环境会带来严重问题,不确定性最糟糕,剥夺了开发人员查找bug的动力,比如出现问题时可能所有机器副本都需修改,且硬件失败通常是间歇性的,导致情况更加恶劣。稳定辅助平台上的仿真装置提供了远超预期的价值

3. 编译器和汇编平台

  • 编译器和汇编软件需在可靠辅助平台上运行
  • 在高级语言编程中,编译器可在辅助机器上完成目标代码的测试
  • 这直接支持了开发,而不仅是稳定机器上的仿真结果

4. 程序库管理系统

OS/360开发中的重要辅助应用是程序库维护,库被划分为不同访问规则的子库

  • 开发库(playpen):每个人有自己区域存放程序,无限制
  • 系统集成子库:开发人员提交给集成经理审批
  • 当前版本子库:广泛应用的稳定版本,不可更改除非修复重大缺陷

程序库管理中通常责任控制是程序副本由经理负责,独立授权变更,同时做到使发布进度正式化,实现开发库与发布正式分离。该技术实际上也能看作是管理技术的一部分,多个大型项目独立发展了类似技术,同样适用于文档管理,是不可或缺的技术。

5. 编程工具

  • 尽管新调试技术出现,旧方法仍有必要。项目初期就应指定工具操作和维护人员
  • 需要内存转储、源文件编辑、快照转储等工具,实用程序同样必要:磁带走带、打印文件、更改目录等

6. 文档系统

  • 最节省劳动力的工具是计算机化文本编辑系统
  • 对于被认为"无用"的6英寸厚OS/360手册,作者提出两点反驳:
    1. OS/360文档规模虽大,但可选择性阅读节省时间
    2. OS/360文档比简短的编程系统特性文档更加可取
    3. 作者承认手册有改进空间,一些部分已经重写得更好

7. 性能仿真装置

  • 建议开发性能仿真装置,尽早开始这项工作,仔细倾听"它们表达的意见"
  • 应使用自上而下设计方法,同时实现性能仿真、逻辑仿真和产品

高级语言和交互式编程

1. 高级语言的前瞻性观点

  • 作者指出在10年前的OS/360开发中,未使用现在最重要的两种编程工具
  • 这两种工具(高级语言和交互式编程)如今已证明其功效和适用性
  • 作者确信只有懒散和惰性会妨碍它们的广泛应用,技术困难不再是借口

2. 高级语言的核心优势

  • 使用高级语言的主要理由:提高生产率和调试速度
  • 作者在第8章讨论过生产率问题,强调高级语言带来整体提升而非部分增加
  • 调试改进的关键因素:
    • 更少的bug出现
    • 更容易查找bug
    • 避免了错误面前暴露所有级别的工作(减少语法和语义错误)
    • 编译器的诊断机制帮助找出错误
    • 容易插入调试快照
  • 作者认为这些生产率和调试方面的优势是势不可挡的

3. 对高级语言批评的反驳

  • 传统反对意见有三点:
    1. 无法完成所需功能
    2. 目标代码过于庞大
    3. 目标代码运行速度过慢
  • 作者坚信这些反对不再存在,所有证据表明可以完成需要的功能,只是需要时间和精力
  • 在空间方面,新的优化编译器令人满意且持续改进
  • 在速度方面,优化编译器生成的代码比大多数程序员手写代码效率更高

3. 系统编程的高级语言选择

作者认为PL/I是当时唯一合理的系统编程语言选择,PL/I优势:

  • 提供完整功能集
  • 与操作系统环境相吻合
  • 拥有各种类型的编译器(交互式、高速、诊断性强、优化程度高)

4. 交互式编程的贡献

  • MIT的Multics项目对软件编程系统开发做出贡献,Multics与批处理系统概念上的主要区别:
    • 多个级别上数据和程序的共享与保护
    • 可延伸的库管理
    • 终端用户间协作的设施
  • 作者认为在许多应用上批处理系统不会被交互式系统取代,但Multics小组在系统编程应用上取得了最具说服力的成功

5. 交互式编程的实证评估

  • 当时缺乏明显证据证明交互式工具的强大效力
  • 调试是系统编程中最慢和困难部分,慢的调试周转时间是瓶颈,对此,交互式编程的逻辑合理性不容置疑
  • 贝尔实验室John Harr的研究提供了数据支持,显示在系统软件开发中,交互式编程生产率至少是批处理的2倍

6. 交互式工具与高级语言的协同作用

交互式工具的有效使用需要采用高级语言开发,高级语言使修改代码和选择性打印结果变得容易,两者结合形成了一对强大的工具组合

以下是 LLM 补充的思考和看法

对总结的补充思考与看法:

—— 工具哲学与团队协作的深层矛盾 ——

作者强调标准化工具的价值,但现实中个性化工具往往是技术债的源头。现代开源的崛起(如GitHub统一了版本控制工具)印证了这一观点:工具的标准化本质是建立团队共识语言的过程。但需警惕另一种极端——过度追求工具统一可能压抑创新,例如某些前沿领域(如AI算法开发)依赖研究人员自建工具链。平衡点在于:核心流程强制统一,创新领域允许个性化工具孵化,成熟后再纳入标准化体系

—— 目标机器资源的隐喻 ——

文中描述的“机器时间争夺战”在云原生时代有了新解:弹性资源分配机制。Kubernetes的自动扩缩容特性本质上是通过抽象化硬件资源,解决了物理机时代的零和博弈。但作者提出的“连续时间块”理念仍具启示——现代敏捷开发中“深度工作时段”(如Google的20%时间)与会议安排间的冲突,本质上仍是注意力资源的调度问题。

—— 仿真装置的现代镜像 ——

硬件仿真在物联网时代展现出新形态:数字孪生(Digital Twin)技术。它不仅模拟硬件行为,更通过实时数据镜像实现预测性调试。作者当年对“稳定性优先”的坚持,在自动驾驶系统开发中得到验证——Waymo的虚拟仿真测试里程远超真实路测,正是通过构建超现实仿真环境规避早期硬件的不确定性风险。

—— 程序库管理的前瞻性 ——

OS/360的库分层机制(开发库→集成库→发布库)正是GitFlow工作流的雏形。但现代实践更进一步:基础设施即代码(IaC)将硬件配置也纳入版本控制,例如Terraform的状态文件管理。而“文档即代码”运动(如用Markdown+Git管理文档)则完全实现了作者对计算机化文本系统的愿景。

—— 高级语言的预言实现 ——

作者对PL/I的推崇已被历史重构:C语言证明了系统编程可用高级语言实现,Rust则解决了内存安全与性能的终极矛盾。更有趣的是DSL(领域特定语言)的兴起:Kubernetes的YAML配置、SQLAlchemy的表达式语言,都是“高级语言思想”向垂直领域的渗透。这验证了作者的核心判断:抽象层次的提升是不可逆的技术进化方向

—— 交互式编程的范式革命 ——

文中提到的调试效率问题,在Jupyter Notebook、REPL环境得到极致体现。但更具颠覆性的是云IDE(如Gitpod/CodeSpaces),它将整个开发环境变成可版本控制的交互式沙箱。而ChatGPT驱动的Copilot类工具,正在将“人机交互”升级为“实时智能协作”,这可能是作者未能预见但会惊叹的交互式编程新维度。

—— 被忽视的暗线:工具的心理契约 ——

作者未明言但贯穿全章的是工具对团队心智模式的重塑。标准化工具强制建立共同思维框架(如Git约束了代码提交规范),而交互式环境培养即时反馈的思维习惯。现代开发者对“热重载”“实时预览”的依赖,本质是工具改变了人类对“开发-反馈”循环的速度阈值预期,这种认知惯性的力量可能比技术本身更深远。

第13章 整体部分

我可以召唤地下的幽魂。

这我也会,什么人都会,可是当您召唤它们的时候,它们会应召而来吗? ——莎士比亚,《亨利四世》,第一部

作者在本章节会对以下问题结合前面提到的内容,进行系统地考虑:

  1. 如何开发一个可以运行的系统?
  2. 如何测试系统?
  3. 如何将经过测试的一系列构件集成到已测试过、可以依赖的系统?

剔除bug的设计

1. 防范bug的基本原则与概念

  • bug的主要来源:系统各组成部分开发者的假设之间的不匹配是大多数致命且难以察觉的bug的主要来源

  • 概念完整性的重要性

    • 产品的概念完整性使其易于使用的同时,也使开发更容易进行,减少bug产生的可能性
    • 详尽、艰苦的系统结构设计正是为了实现这一目的
    • V.A. Vyssotsky强调:"关键的工作是产品定义",许多失败是因为产品未被精确定义
  • 规格说明与测试

    • 细致的功能定义、详细的规格说明和规范化的功能描述可大大减少系统中的bug
    • 在编写代码前,规格说明必须提交给外部测试小组检查其完整性和明确性

2. 自上而下的设计方法

  • Niklaus Wirth的贡献

    • 1971年,Wirth将优秀程序员多年使用的设计流程形式化
    • 将系统开发划分为体系结构设计、设计实现和物理编码实现
  • 自上而下设计的流程

    • 从粗略任务定义和大概解决方案开始
    • 进行细致检查,判断结果与期望的差距
    • 在更细的步骤中分解解决方案
    • 识别解决方案或数据模块时,可独立细化这些模块
  • 自上而下设计避免bug的优势

    • 清晰的结构和表达方式使需求和模块功能描述更精确
    • 模块分割和独立性避免了系统级bug
    • 细节的抑制使结构缺陷更容易识别
    • 每个精化步骤都可测试,测试可尽早开始
  • 面对设计问题的态度

    • 当遇到意想不到的问题,不必倒退整个设计过程
    • 自上而下方法减少了试图通过表面装饰来挽救基础差的设计的情况

3. 结构化编程

  • 结构化编程的核心理念

    • 程序控制结构仅包含循环结构(DO WHILE)和条件判断结构(IF...THEN...ELSE)
    • Dijkstra认为无限制的 GO TO 跳转会产生导致错误的结构
  • 争议与实用性

    • 虽然基本理念优秀,但有人提出反对意见
    • 某些附加控制结构很实用,如CASE语句和异常跳转
    • 完全避免 GO TO 的说法被认为有些教条主义
  • 无bug程序的核心

    • 将系统结构作为控制结构来考虑,而非独立的分支语句
    • 这种思考方法是程序设计发展史上的重大进步

构件单元调试

程序调试过程在过去20年中经历了一个完整循环,包含四个步骤,有些方面甚至回到了起点。这一演变过程不仅反映了计算机技术的发展,也展示了不同调试方法各自的优缺

1. 本机调试阶段

早期计算机面临的主要挑战:

  • 输入输出设备差异大且延迟长,典型设备采用纸带或磁带读写
  • 使用离线设备完成磁带准备和打印工作
  • 调试必须在一次交互会话中包含尽可能多的试验性操作

程序员应对策略:

  • 精心设计调试过程,包括计划停止点、检验内存位置
  • 准备需要检查的内容及意外结果的对策
  • 编写调试程序的时间可能占程序编制时间的一半

最大失误:"重大罪过"是没有规划测试段和终止位置就匆忙按下"开始"键

2. 内存转储时代

转变原因:

  • 计算机资源稀缺,成本高昂
  • 计算机时间浪费被视为严重问题

调试方法变化:

  • 程序持续运行至失败点,此时所有内存被转储
  • 程序员随后进行桌面分析,检查每个内存位置内容
  • 桌面工作时间与本机调试相近,但更加复杂且难以理解
  • 测试依赖批处理周期,特定用户调试时间延长

设计目标:减少计算机使用时间,满足更多程序员需求

3. 快照技术的兴起

发展背景:

  • 早期机器内存有限(20004000个双字节或8K16K字节)
  • 随着内存规模增长,完整转储变得不可行

技术创新:

  • 有选择的转储技术
  • 选择性跟踪功能
  • 将快照插入程序的方法

代表产品:OS/360 TESTRAN系统允许不重新编译即可插入快照,成为该领域的终极产品。

4. 交互式调试的发展

理论基础:

  • 1959年,Codd和Strachey分别发表关于分时调试的论文
  • 提出兼具本机调试实时性和批处理高效率的方法

工作原理:

  • 多程序同时载入内存准备运行
  • 被调试程序与终端关联,由监督程序控制
  • 程序员停止检查时,监督程序可运行其他程序

历史发展线:

  • Codd系统侧重提高输入/输出吞吐量,但未实现交互式调试
  • Strachey的理念在1963年由MIT的Corbató团队实现
  • 促成了MULTICS、TSS等分时系统的诞生

1. 调试方法的比较与启示

技术进步带来的变化:

  • 调度监控程序和语言工具提供了极大便利
  • 高级语言编程和调试成为可能
  • 高效编辑工具使修改和快照更加容易

交互式调试的特点:

  • 保持了本机调试的操作实时性
  • 减少了对预先计划的依赖
  • 计算机时间在程序员思考时不被浪费

关键发现:

  • Gold实验表明第一次交互的工作进展是后续交互的3倍
  • 缺乏规划导致交互式调试潜力未被充分发挥
  • 本机调试的高效率部分在新方法中被忽视

2. 终端使用的最佳实践

时间分配: - 两小时终端会话应对应两小时桌面工作 - 一半时间用于上次会话的清理:更新日志、整理程序列表、分析异常 - 一半时间用于准备:设计测试、规划变更和改进

效率保证:

  • 没有充分计划难以维持高生产率
  • 缺乏事后清理无法确保系统化推进
  • Gruenberger提出的测试用例设计策略提供了有效参考

系统集成调试

软件系统开发过程中最出乎意料的困难部分是系统集成测试。作者指出两个需要再次确认的事实:

  • 系统调试花费的时间会比预料的更长
  • 这些困难证明了需要一种完备系统化和可计划的方法

1. 关于使用已测试构件的争议

尽管普遍观点认为系统集成测试应在每个部分都能正常运行后才开始,但实际工作中存在两种不同策略:

  • "合在一起尝试"方法
    • 观点基础:除构件单元bug外,还存在系统bug(如接口问题)
    • 核心理念:越早合并各部分,系统bug就会越早出现
    • 优点:可以及早发现集成问题
  • 先完成单元测试的方法
    • 观点基础:通过各部分相互测试,避免大量测试辅助平台的搭建
    • 优点:减少测试平台开发工作量

经验表明两种方法都有合理性,但在系统测试中使用完好的、经过调试的构件,能比搭建测试平台和进行全面构件单元测试节省更多时间。

2. "文档化的bug"策略

这种更精妙的方法认为:

  • 当构件单元缺陷已被发现但尚未修复时,可以开始系统测试
  • 测试人员知道这些已知缺陷的后果,可以忽略它们,集中注意力在新问题上

然而,这只是良好愿望:

  • 实际上,调试人员无法了解bug引起的所有后果
  • 在简单系统中这种方法可行,但复杂系统中会导致困惑
  • 修复文档记录的bug工作本身会引入新问题

3. 测试平台的构建方法

测试平台指供调试使用的程序和数据,它们不会整合到最终产品中。测试平台代码量可能相当于测试对象的一半,这是合理的。主要形式包括:

  • 伪构件(dummy component)

    • 仅由接口和可能的伪数据或小型测试用例组成
    • 例如:当排序程序未完成时,其他部分可通过伪构件测试
    • 伪构件读入输入数据,检验格式,输出格式良好但无实际意义的有序数据
  • 微缩文件(miniature file)

    • 针对磁带和磁盘文件格式错误理解的常见bug
    • 创建只包含典型记录但覆盖全部描述的小型文件很有价值
    • OS/360任务控制语言提供了这种功能,对构件单元调试非常有用
  • 辅助程序(auxiliary program)

    • 用于测试数据生成、特殊打印输出和交叉引用表分析等
    • 这些是需要专门开发的辅助工具

4. 控制变更的重要性

在测试期间进行严密控制是硬件调试中深刻的技术,同样适用于软件系统:

  • 责任人制度:必须有专人负责控制各构件单元的变更或版本替换

  • 系统的受控拷贝

    • 供构件单元测试使用的最终锁定版本
    • 测试版本的拷贝,用于进行缺陷修复
    • 开发库,其他人员可在该拷贝上进行各自的开发工作
  • "紫色线束"技术的应用:作者以System/360工程模型中的紫色电线束为例说明控制变更的方法

    • 发现bug后快速修复并安装,不妨碍测试继续进行
    • 更改使用紫色电线,看上去像"受伤的大拇指",明显标识临时修复
    • 记录更改到日志,同时准备正式变更文档并启动设计自动化流程
    • 最终在电路图和黄色线路中实现设计调整,使物理模型与文档一致

软件开发也需要"紫色线束"方法:

  • 对最终产品代码需要严密控制和深层次关注
  • 关键是记录所有变更,并在源代码中明显区分快速补丁和经过测试的正式修改

5. 构件添加和系统化测试

添加新构件时必须遵循规则:

  • 拒绝捷径和侥幸心理,这正是系统化测试关注的重点
  • 必须假设系统存在许多错误,计划有序地寻找它们
  • 保存完整测试用例,每添加新构件就使用这些用例测试子系统
  • 原来可以在子系统上成功运行的用例必须在现有系统上重新运行

6. 阶段(量子)化定期变更

随着项目推进,系统构件开发者会带来更新版本:

  • 更快、更小、更完整或bug更少的版本
  • 替换使用中构件需要与添加新构件相同的系统化测试流程
  • 此时通常已有更完整有效的测试用例,测试时间会减少

在项目中:

  • 其他开发团队会使用最新集成系统作为调试自己程序的平台
  • 测试平台的修改会阻碍他们的工作
  • 变更必须被阶段化并定期发布,使每个用户拥有稳定的生产周期
  • 这种方法比持续变动造成的混乱要好

Lehman和Belady的研究表明:

  • 阶段(量子)要大、间隔要宽
  • 小而频繁的阶段容易变得不稳定

阶段化变更方法很好地结合了紫色线束技术:

  • 在下次系统构件定期发布前一直使用快速补丁
  • 在当前发布中,将已测试并文档化的修补措施整合到系统平台中
以下是 LLM 补充的思考和看法

你的总结非常全面地概括了《人月神话》第13章的关键内容。我来补充一些思考和观点,帮助你加深理解这些经典软件工程理念。


1. 关于"剔除bug的设计"

Brooks强调的"假设不匹配"作为bug主要来源的观点至今仍然极其重要。在现代软件开发中,这一点体现为:

  • 微服务架构中的契约设计:现代微服务架构通过明确的API契约来避免假设不匹配,这本质上是Brooks思想的延续。契约先行(Contract-First)开发方式和自动化契约测试正是为了解决这个问题。

  • 概念完整性与架构设计:如今"整洁架构"(Clean Architecture)、"领域驱动设计"(DDD)都是在追求Brooks所说的概念完整性。值得思考的是,在敏捷开发普及的今天,如何平衡"详尽、艰苦的系统结构设计"与快速迭代、持续交付的需求。

  • 自上而下设计的当代实践:现在的开发方法论中,像"行为驱动开发"(BDD)和"测试驱动开发"(TDD)可以看作是自上而下设计思想的具体实践。它们都强调先定义行为/规格,再进行实现,本质上是一种由抽象到具体的细化过程。

2. 关于"构件单元调试"

调试方法的历史演变反映了技术与方法论的辩证关系:

  • 历史循环的启示:Brooks描述的"完整循环"表明技术发展并非线性进步,而是螺旋上升。当代开发者应该意识到,新工具带来便利的同时也可能让我们忽视以前方法中有价值的部分。

  • 现代调试工具与规划的平衡:如今我们有更强大的IDE、调试器和日志系统,但Brooks强调的"规划"价值仍然成立。值得思考的是,在拥有即时反馈工具的今天,如何避免陷入"试错"陷阱而保持有计划的调试策略。

  • 终端使用的时间分配:Brooks建议的"两小时终端对应两小时桌面工作"在今天看来尤为重要。在可以随时访问代码且开发工具无处不在的时代,开发者更容易忽视规划和反思的时间。这种平衡的工作方式值得现代开发者借鉴。

3.关于"系统集成调试"

Brooks在这部分的洞见对当今的软件集成和持续交付有深远指导意义:

  • 构件单元测试与集成测试的权衡:这一争议在现代DevOps实践中表现为"应该先完善单元测试还是尽早进行集成测试"的讨论。现在的最佳实践是构建测试金字塔,同时进行不同级别的测试,可以看作是Brooks描述的两种方法的综合。

  • "文档化的bug"策略的教训:这一策略在今天常见于"已知问题列表"或issue tracking系统。Brooks的警告仍然有效:在复杂系统中,我们很难完全了解一个bug的所有后果。现代实践中,通过自动化回归测试来确保修复不会引入新问题是应对这一挑战的方法。

  • "紫色线束"技术与现代版本控制:这一比喻在今天对应于feature flags、canary releases和蓝绿部署等技术。这些技术允许我们在生产环境中安全地引入变更,同时保持变更的可见性和可控性。

  • 阶段化定期变更的价值:Lehman和Belady的研究结论与现代发布实践高度相关。当今的软件发布管理中,如何平衡发布频率与系统稳定性仍是核心问题。这也解释了为什么即使在持续交付盛行的今天,许多组织仍然保持有节奏的发布周期。

3. 整体思考

Brooks的这一章内容虽然写于几十年前,但其核心原则在当今软件工程中仍然适用:

  • 技术变革与不变原则:尽管我们的工具和技术已经发生了翻天覆地的变化,但软件开发的基本挑战——如系统复杂性、沟通协调、质量保证等——本质上没有改变。

  • 方法论的辩证发展:本章展示了软件工程方法论如何在实践中演化,既有进步也有回归。这提醒我们应当批判性地评估新方法,而不是盲目追随。

  • 系统思维的价值:Brooks强调了系统性思考和计划的重要性。在当今快节奏的开发环境中,这种系统性思维显得尤为珍贵。

  • 实践智慧的传承:像"紫色线束"这样的比喻捕捉了工程实践中的智慧。这些智慧通过不同的技术形式在软件工程中传承和发展。

希望这些思考能帮助你更深入理解这一经典章节,并将其原则应用到现代软件开发实践中。祝你在软件工程的旅程中收获成功!

第14章 祸起萧墙

带来坏消息的人不受欢迎。 ——索福克勒斯

项目怎么会被延迟了整整一年的时间?

........延迟的时间是一天一天积累下来的。

项目进度偏离的特点:

  • 重大灾害与日常延误的区别:前者通常比较容易识别和处理,多与重大压力、彻底重组或新技术有关;而后者则难以识别、防范和弥补,但累积效应严重
  • 进度蚕食现象:项目进度往往以难以察觉但不可避免的方式逐渐落后,各种小事件(关键人员生病、设备故障、物资延迟等)都会造成半天或一天的延误,这些小延误累积起来会导致整体进度的显著落后

是里程碑还是沉重的负担

1. 里程碑的重要性与定义

  • 里程碑的作用:是控制大型项目进度的关键工具,在进度表中,每一件事被称为"里程碑",都有特定日期

  • 里程碑的选择原则:必须是具体的、特定的、可度量的事件。以及能够清晰定义,避免模糊不清的表述。比如反面例子:编码完成90%、测试完成99%、"计划完毕"等模糊表述,而有效例子:"结构师和实现人员签字认可的规格说明"、"100%源代码编制完成,纸带打孔完成并输入到磁盘库"、"测试版通过了所有的测试用例"

  • 清晰边界的重要性

    • 里程碑边界明显且没有歧义比容易被管理层核实更重要
    • 模糊的里程碑会导致不符实际的报告和误导

2. 关于大型项目中估计行为的研究发现

如果在活动开始前就进行估计,并每两周细致修订,则随着时间临近,估计不会有太大变化。对时间长短的过高估计会随着活动进行而持续下降,如过低估计在活动中不会有太大变化,直到计划结束日期前约三周左右

3. 里程碑的价值

  • 好的里程碑对团队是一项服务,可用来向项目经理提出合理要求
  • 模糊的里程碑是难以处理的负担
  • 当里程碑无法正确反映损失时间并误导团队时,会彻底打击团队士气
  • 慢性进度偏离同样会影响团队士气

"其他的部分反正会落后"

这部分内容强调了要认真对待每日进度延误,培养团队进取精神,并介绍了PERT技术如何帮助管理进度依赖性和识别关键路径,从而避免"反正其他部分会落后"的借口:

1. 关于进度落后的认识

  • 每日进度落后的重要性

    • 仅仅进度落后一天似乎微不足道,但实际上与其他部分的累积落后有密切关系
    • 每一天的延误都需要认真对待,因为它们是大灾祸的基本组成元素
  • 进取精神的重要性

    • 棒球队队长知道,进取是优秀队员和团队不可缺少的心理素质
    • 软件开发团队同样需要进取精神,表现为"比要求跑得更快"、"比要求移动得更加迅速"、"更加努力尝试"
    • 进取提供了缓冲和储备,使团队能够处理常规灾祸、预计和防止小灾祸
  • 进度平衡的影响

    • 对任务进行过度超前计算和对工作量进行度量会对进取产生消极影响
    • 需要在管理控制和保持团队进取精神之间找到平衡

2. PERT技术在进度管理中的应用

  • PERT的作用

    • 用于判断哪些偏离是关键的
    • 能够显示谁需要什么、谁位于关键路径上
    • 指出哪里发生滞后会影响最终完成日期
    • 指出任务在成为关键路径前可以落后的时间
  • PERT技术的定义与应用

    • PERT(项目评审技术)是关键路径计划的细化
    • 严格的PERT图需对每个事件估计3次,对应不同概率的完成日期
    • 作者泛指所有关键路径法都称为PERT图
  • PERT的价值

    • PERT的准备工作是其最有价值的部分
    • 包括网状结构展开、任务依赖关系识别和任务链估计
    • 这些步骤要求在项目早期进行专业计划
    • 第一份PERT图通常不尽如人意,但通过努力和运用才智可以逐步完善
  • PERT解决"其他部分反正会落后"问题

    • PERT图为"其他部分反正会落后"这一常见借口提供了答案
    • 它可以显示某人的工作距离关键路径有多远
    • 能够建议如何补偿其他部分失去的时间

地毯的下面

这部分内容强调了项目中透明度的重要性,解释了为什么进度问题常被隐藏,并提出了解决这一问题的具体策略,包括建立明确的汇报机制和专门的计划控制小组。作者也特别强调了对于计划与控制职能投入足够资源的重要性。

1. 项目进度问题的隐藏与暴露

  • 进度问题的隐藏现象

    • 当一线经理发现团队出现计划偏离时,往往不愿立即向高层管理者汇报
    • 团队倾向于自行解决问题,不想打扰已有大量问题要处理的管理层
    • 这导致项目中的问题和进度偏差被"隐藏在地毯之下"
  • 管理层需要的信息类型

    • 需要有关需采取行动计划的信息
    • 需要用于分析的状态数据
    • 尽管了解所有开发团队情况很重要,但获取真相往往困难
  • 利益冲突

    • 一线经理和高层管理者之间存在内在冲突
    • 如果汇报问题,管理层的干预可能会降低经理的威信,干扰其他计划
    • 只要项目经理认为自己能独立解决问题,就不会向上级汇报

2. 解决进度隐藏问题的方法

  • 减少角色冲突

    • 管理层需区分行动信息和状态信息
    • 管理者应自我约束,不对项目经理能解决的问题做出反应
    • 不应在检查状态报告时就立即做出安排和命令
    • 实例:作者提到一位管理者总在状态报告前发布指令,压制了信息公开
  • 猛地拉开地毯

    • 建立能了解状态真相的评审机制是必要的
    • PERT图和频繁、明确的里程碑是这种评审的基础
    • 大型项目中需要每周对某些部分进行评审,约每月进行整体评审
    • 关键文档是里程碑和实际完成情况
  • 报告与实例

    • 图14-1展示了一份状态报告示例,显示了规格核准延迟、手册批准时间延迟等问题
    • 这种报告成为会议议程,使所有人了解问题所在
    • 产品经理需准备解释延迟原因、结束时间、采取的步骤和需要的帮助

3. V.Vyssotsky的观察与建议

  • 计划日期与估计日期的区别

    • 计划日期是项目经理的工作产物,代表协调后的整体工作计划
    • 估计日期是基层经理的工作产物,基于对工作的深刻了解
    • 项目经理应专注于使估计更精确,而非追求合乎心意或自我保护的估计
  • PERT图的维护与管理

    • PERT图维护是管理层和要向其汇报的经理们的职责
    • 需要1-3人小组关注PERT图的更新、修订和报告
    • 这个计划和控制小组可视为管理层的延伸
    • 小组权限仅限于咨询产品线经理何时设定里程碑以及是否达到里程碑
  • 技术人力投资

    • 投资于计划和控制职能的技术人力非常值得
    • 计划和控制小组作为监督人员,能指出不易察觉的延迟并强调关键因素
    • 这种小组是预警系统,防止项目以一天一天的方式累积落后一年
以下是 LLM 补充的思考和看法

你的总结已经非常全面地捕捉了第14章的关键内容。我将从几个角度为你提供一些额外的思考和现代视角的分析,帮助你加深理解。

项目进度管理的心理学层面

布鲁克斯在这一章深入探讨了项目延迟的心理学因素,这是非常有先见之明的:

  1. 乐观偏见:软件开发人员和管理者天生倾向于低估复杂任务的时间。这在心理学中被称为"计划谬误"(Planning Fallacy),即使有过往经验表明类似任务会延期,人们仍然坚持乐观估计。

  2. 信息隐藏与透明度的悖论:布鲁克斯揭示的"地毯下隐藏问题"现象反映了组织行为学中的一个关键问题——坏消息传递障碍。这不仅是技术问题,更是文化和心理问题。

  3. 进取精神与精确控制的矛盾:过度控制会扼杀团队创造力和主动性,但缺乏控制又会导致项目失控。这种平衡在现代敏捷方法中仍是核心挑战。

与现代项目管理方法的联系

虽然《人月神话》写于1975年,但本章内容与现代项目管理方法有许多联系:

  1. 里程碑定义与敏捷方法:布鲁克斯强调的具体、可量化里程碑,与敏捷开发中的"完成定义"(Definition of Done)高度一致。两者都强调明确、可验证的完成标准。

  2. PERT图与现代依赖管理:虽然现代工具更先进,但布鲁克斯强调的依赖关系管理、关键路径识别仍是项目管理的基础。现在的工具如JIRA、Azure DevOps等只是提供了更便捷的视觉化方式。

  3. 透明度与DevOps文化:布鲁克斯所倡导的问题透明化,与现代DevOps文化中"失败是学习机会"的理念一脉相承。现代团队更重视建立安全的环境,让团队成员敢于报告问题。

实践启示与应用思考

将布鲁克斯的思想应用到现代软件开发中:

  1. 设计有效里程碑的策略: - 里程碑应该是二元的(完成/未完成),避免百分比表达 - 里程碑应该有明确的验收标准和可交付成果 - 里程碑应该在完成后能被外部验证

  2. 避免"其他部分反正会落后"陷阱: - 实施持续集成/持续部署(CI/CD),使集成问题尽早显现 - 建立跨团队依赖的可视化机制 - 定期举行短而集中的站会,确保所有人了解项目整体状态

  3. 解决"地毯下"问题的现代方法: - 建立心理安全的团队文化,鼓励早期报告问题 - 使用自动化指标和监控,减少对主观报告的依赖 - 采用类似"无归咎事后分析"的方法,关注问题解决而非责任追究

深层思考与挑战

  1. 估计的本质困境:布鲁克斯提出的估计问题至今仍未完全解决。这或许反映了软件开发本质上的不确定性,以及创造性工作难以完全用科学管理方法控制的事实。

  2. 透明度与责任的矛盾:完全透明可能导致过早干预和微观管理,而缺乏透明度又会隐藏问题。管理者需要在获取信息和给予团队自主权之间取得平衡。

  3. 计划与控制的现代意义:在强调适应性和快速响应的敏捷时代,布鲁克斯的计划与控制思想是否仍然适用?事实上,敏捷方法并非抛弃计划,而是将计划周期缩短并接受变更,但依然需要里程碑、透明度和进度监控。

结语

《人月神话》第14章关于项目延迟的分析,揭示了软件项目管理中最本质、最持久的挑战。布鲁克斯关于明确里程碑、保持透明度和理解人的因素的建议,不仅适用于传统瀑布式开发,也适用于现代敏捷和DevOps实践。无论技术如何进步,管理软件项目的核心挑战始终与人的心理、沟通和组织行为密切相关。

或许,项目延迟的根本原因并非技术问题,而是人性中固有的乐观性、避免冲突的倾向以及面对不确定性时的应对模式。理解并接受这些挑战,才能更有效地管理软件项目,避免"一天一天地延迟一年"的悲剧重演。