《人月神话》读书笔记(上):焦油坑、人月与团队
大型软件为什么会拖成怪物,答案不在技术而在组织——加人救不了落后的项目,能救的是拆解、少而精的团队和一个人说了算的概念一致性。
一本 1975 年的书,讲的却是我每年都要重新踩一遍的坑。这是我读《人月神话》的笔记上篇,讲一件事:大型软件为什么难,以及人该怎么组织。沟通、拥抱变化和”没有银弹”留到下篇。
正文是布鲁克斯的观点,引用块里是我结合自己做数据、做工程时的批注。
焦油坑
大型软件产品的开发就像巨兽陷进焦油坑。通常是一堆各自都好解决的小问题连在一起,凑成一个复杂度超高、谁都啃不动的大问题。从一个能跑的 demo 到真正产品化,往往要花 9 倍的时间。
但程序员这份职业的乐趣,恰恰是这种创造本身:把想法照进现实并分享出去,把零件拼起来看它按预想运行,不断学新东西而不是重复劳动,还能拿到实际的收益。焦油坑很黏,可爬的人是自愿下去的。
人月神话
向进度落后的项目中增加人手,只会使进度更加落后。——Brooks 法则
程序员估算开发周期时,会陷进一种乐观主义,它有四张面孔。
第一张,以为一切都会顺。我们总期待每项任务只花”应该”花的时间。创造性活动分构思、实现和交流,而编程是种容易上手的介质,让人以为实现不会卡壳。可构思本身是有缺陷的,所以 bug 永远都在。
第二张,以为人和月可以互换。进度一偏,下意识就想加人。但项目熟悉度、人员能力的差异,让人和月根本换不了,加人甚至有一定概率把项目拖得更差。“人月”这个工作量单位暗示两者能替换,这是带欺诈性的。尤其当任务因为先后次序不能拆分时,加人对进度毫无帮助——想想那张排满依赖的图。
第三张,把进度和工作量混为一谈。装软件时最常卡在 99%,因为最后的 1% 要付出的努力远高于最初的 1%。行百里者半九十,进度不等于工作量。
第四张,缺乏跟踪。排期到一半,进度常常还没到一半。跟踪要靠数据和看板,要定期开会对齐。真落后了,办法无非两个:加班,或者做减法——先拆解,只上线最核心的部分。
系统测试
因为盲目乐观,真出现的缺陷总比预料的多得多,于是系统测试的排期常常是整个项目里最不合理的一段。
我们太自信,给测试留的时间太少。测试的时间应当大于编码的时间。而且不能等软件做完才测,任何一行代码、任何能产出结果的东西,一写出来就该测。
外科手术队伍
高效率,小团队,协作分工。
先看一个残酷的差距:研究里最好和最差的程序员,生产率平均差 10 倍,运行速度差 5 倍。换句话说,一个顶十个不是修辞。
由此推出一个判断:需要协作的人越多,成本越高,所以系统应当由尽可能少的人来开发。可这里有个两难——小团队效率高但对大型系统太慢,大团队能扛大型系统但成本高、效率低。论效率和概念完整性,最好让少数干练的人来做;论交付时间,又不得不上大量人手。
系统级别的管理和拆解问题,远比一个人解决问题重要得多。
这句话我深有感触。当你把事情拆得足够简单明了,又有足够强的跟踪和管理能力,人手甚至可以随意配置。会解决问题的人不一定会拆解问题,但会拆解问题的人,极大概率能一个人把问题解决——这是一种更高级的能力。
布鲁克斯引用 Mills 的建议:大型项目的每一部分交给一个像外科手术台那样组建的小队,而不是一拥而上。台上有主刀的首席程序员,有随时能顶上的副手,还有管理、文档、测试、工具、语言专家等各司其职的角色,全都围着主刀转。

两条原则撑起这套结构:主刀和副手要吃透整个系统的运作细节和业务;任何决策由主刀一个人统一,以此保证客观一致性。今天互联网公司推崇的”高效率、小团队、协作分工”,其实就是这套。
谁来保证大家想的是同一个东西
没有规矩,不成方圆。
外科手术队伍效率最高、又能兼顾交付,可当你有很多支这样的队伍时,新问题来了:怎么保证他们脑子里的概念是一致且完整的?
拿我做过的产品打个比方。一个产品线分成几个子版本、几个团队,它们认知上是同一个产品吗?其实不是,是互相借鉴的多个产品。但每个版本内部又分很多迭代方向,这时就必须遵守一套连贯的设计思路——版本内部需要概念的一致性和完整性。
概念的完整性来自一件事:每个部分都反映相同的原理、原则和一致的折衷机制。就像前后端约定好返回结构、状态码含义,或者统一走一种接口风格——不遵守也能实现功能,但遵守了才谈得上协作。
布鲁克斯说得更狠:概念完整性要求设计出自一个人,或少数有默契的人。要得到它,必须有一个人来把关这些概念,这是一种”无需任何歉意的贵族专制”。今天流行敏捷、小模块累加,好处是迭代快、能更早发现缺乏统一架构的毛病;但拿到完整概念之后再垂直切分团队,整体反而更快——资深架构师定整体设计,切成几个领域后,实现者在约定框架里自己把控细节。
画蛇添足
抓住重点,把握关键,永远避免画蛇添足。
看建筑行业里结构师怎么和承包商打交道:结构师用估算编预算,承包商报价来验证。报价几乎总会超预算,于是结构师要么改预算、要么改设计,要么建议换更便宜的做法。
这几乎就是研发和产品的关系。产品出方案、编”预算”,研发来修正。排期超了,产品要么砍需求、要么加排期,也可以让研发用更快但牺牲一点质量的方案先上。
由此有几条准则:架构师只能建议、不能支配,代码的创造性握在程序员手里;随时准备预案,接受任何能达到目标的其他方法;对自己的建议保持低调,也随时准备放弃它。开发常反对体系结构的修改,而且往往开发是对的,因为实现时总有意想不到的问题。
最危险的系统是第二个。做第一个时人会谨慎,因为知道自己不懂;做第二个时,先前的经验反而喂出过度自信,于是极容易翻车。
我自己就撞过。做第一个数据需求时因为不懂而充满敬畏,技术方案和数据预演都做得很细,上线效果很好;做第二个时因为自信、不够敬畏,有些功能出了问题,第二天才补上修复。Always day 1,说的就是这个。
所以设计系统时别画蛇添足,一心想”支持任何特性”会死得更快。真要改进也别指望一步到位——每次改动的代码量最好不超过整体的 15%,保持创造性,但别过分创造。
贯彻执行
吾尝终日而思矣,不如须臾之所学也。
定义有两种。形式化定义(比如接口规范)精确但难懂;叙述式定义好懂但不精确。一旦要充当标准,就必须用形式化定义——就像用一种强制的接口描述语言当前后端的契约,特别有用。
怎么把它贯彻下去?开周期会同步;实现中按实际情况改需求、改方案,但一定要同步给所有相关的人;再设一个独立的测试小组盯着。这三点是执行落地的必要手段。
下篇继续:从”为什么巴比伦塔会失败”讲沟通,一路到未雨绸缪、祸起萧墙,最后落到”没有银弹”。