跳转到主要内容

第 8 章 · 胸有成竹

系统编程到底要多久?工作量是规模的 1.5 次幂,生产率随交互密度与任务复杂度差出十倍,而高级语言能换来 5 倍。

题记两句话放在一起很有意思:普布里乌斯说"实践是最好的老师",《穷理查年鉴》补上一句——“实践是最好的老师,但智者还能从其他的地方有所收获。“这一章就是作者试图从别人的实践里收获的部分。

这一章讲了什么

开篇两个警告,都是针对估算里最常见的偷懒方式。

第一,不能只估编码再套比率。编码只占整个问题的六分之一左右,编码估计或比率上的误差,放大到整任务上就是荒谬的结论。

第二,不能拿小型独立程序的数据外推。他引 Sackman 等人的报告:规模平均 3200 条指令的程序,单个程序员编码加调试约 178 小时,外推得到每年 35800 条语句;而规模只有一半的程序,花费时间大约只有前者的四分之一,推出来的生产率接近每年 80000 行。“计划、编制文档、测试、系统集成和培训的时间必须被考虑在内”,所以这种外推毫无意义——他用了一个很妙的类比:“就好像把 100 米短跑记录外推,得出人类可以在 3 分钟之内跑完 1 英里一样。”

把这两条警告说清楚之后,他给出第一条实证规律:工作量是程序规模的幂函数。图 8-1 引的是 Nanus 和 Farr 在 System Development 公司的研究,指数约为 1.5:

工作量 = 常数 × 指令数量^1.5

Weinwurm 在 SDC 的另一项研究给出接近 1.5 的指数。这条规律意味着规模翻倍时工作量增长到约 2.8 倍——和"人月可以按规模线性摊"的直觉直接冲突

接下来五组数据从不同角度逼近真实生产率。

Portman 的数据解释了一类系统性偏差。他发现自己的队伍落后进度约一半,每项工作实际耗时约为估计的两倍;而那些估计是很有经验的团队用 PERT 图对人小时细分到数百个子任务做出来的。他要求团队记录时间日志,答案立刻出现:队伍只用了 50% 的工作周做实际的编程和调试,其余时间花在机器当机、高优先级的琐碎工作、会议、文字工作、公司业务、疾病和事假上。也就是说,估算对"每个人年里有技术工作时间"这件事做出了不现实的假设。

Aron 的数据按程序员之间的交互密度分类,给出三个量级差别:

交互程度生产率(指令/人年)
非常少的交互10000
少量的交互5000
较多的交互1500

这些数字只含设计与编程,不含支持和系统测试;作者说把它们除以 2 以计入系统测试,结果与 Harr 的数据非常接近。

Harr 的数据来自贝尔电话实验室电子交换系统,四个程序——两个基本控制程序、两个基本语言翻译程序。生产率的单位是"经调试的指令/人年”,已包含编程、构件测试和系统测试。结果分成鲜明两档:控制程序约 600 指令/人年,语言翻译程序约 2200。四个程序规模相近,差别只在工作组大小、时间长短和模块个数上。作者在这里表现得很克制:因果无法确定——“是控制程序确实更复杂,所以需要更多人?还是因为被分派了过多的人,所以需要更多模块和更多人月?没有人可以确定。“但他强调数据反映的是真实状况,因此 Harr 的贡献是实实在在的。

OS/360 的数据给出同一量级的独立佐证:控制程序组约 600–800 经调试指令/人年,语言翻译组 2000–3000。作者由此提炼出一条粗略的换算原则——编译器的复杂度是批处理程序的 3 倍,操作系统的复杂度是编译器的 3 倍

最后是 Corbato 的数据,也是全章唯一让人振奋的一处。MIT 的 MULTICS(约 100–200 万条指令)报告显示平均生产率为 1200 行经调试的 PL/I 语句/人年。换算时要注意单位差异:每个语句对应手写代码的 3–5 条指令,于是得到两条结论:

  • 对常用编程语句而言,生产率似乎是固定的(这个数字已经包含了注释与可能的错误在内);
  • 使用适当的高级语言,编程生产率可以提高 5 倍

我的判断

  • 这一章性质上是一份数据综述,而不是论证。放在第 7 章之后是有意的:先把"交流与组织"讲透,再谈"要多长时间”,顺序上先讲清楚会决定工作量的因素,再谈量化,避免把估算做成纯数字游戏。
  • 1.5 次幂这条规律是本章最重要的结论,也是最容易被今天的人忽略的。它给出的是非线性:规模翻倍、工作量约 2.8 倍。作者没有解释指数为什么是 1.5,我读到这里的理解是,它把接口、集成与测试随规模增长的速度一并吞进去了——这也解释了为什么它和"复杂度三级跳”(批处理 → 编译器 → 操作系统)的量级判断是自洽的。
  • Portman 的数据今天依然成立,而且很反直觉:估算错一倍,往往不是估错任务,而是估错了可用时间。一半的工作周被非技术事务吃掉,这种偏差在任何按"人年"排的计划里都会隐身。
  • Harr 数据里那个因果不确定,我认为是作者全书最诚实的一段:同一份数据既支持"复杂任务需要更多人”,也支持"派了太多人导致模块变多",他明确说没人能确定。可贵的不是数据,而是他没有借数据去证明自己的观点。
  • Corbato 的"高级语言提高 5 倍"这条结论要小心。它是 1970 年代从汇编到 PL/I 的跨越,今天已经不存在同等量级的代差;把它当成"换更高级的语言就能再提 5 倍"来用,是误读。真正仍然有效的是前半句:对常用语句而言生产率是固定的——也就是说,逐行写的效率很难提高,要提高只能改变"要写多少行"。
  • 我对"编译器 = 3 倍批处理,操作系统 = 3 倍编译器"这条保持怀疑:它更像经验秩序而非测量结论,而且三级跳的边界在任何时代都是主观的。但它作为一种迅速对齐预期的手段仍然有用,前提是不要把它当精确系数。

和我手上的工作有什么关系

  • 我自己最容易犯的错正是本章开头警告的第一条:按"写代码的时间"推算整件事的周期,却漏掉设计、文档、测试、集成与学习成本。按这里的比例,编码只占六分之一——这与我实测的体验接近,只是我以前没有把它写下来当预算依据。
  • Portman 的 50% 对我尤其有用。它给了"为什么感觉没做什么事情一天就过去了"一个量化解释,也提示我在排期时应该按可用技术时间的比例打折,而不是按日历时间乘人数。
  • 1.5 次幂这条,我打算把它用成一个预警指标:当项目规模翻倍时,如果计划只涨了一倍工作量,就该怀疑接口与集成成本被漏算了。对个人项目也一样——加一个栏目、加一门语言,代价不是线性的。
  • “生产率是固定的,要提高就得减少要写的行数"这条结论,直接支持了我现在的工作方式:与其纠结手写的效率,不如把机械的部分交给脚本、模板与 Agent(bin/build.sh、archetypes、批量 OCR 都是我实践这条的产物)。
  • Aron 那三档交互密度给了我一个判断工具:同一件事,在"低交互"和"高交互"之间的生产率能差 6 倍以上。所以当我觉得某个任务特别慢时,先问的不是"我是不是变笨了”,而是"这件事的交互密度是不是被低估了"。

一句话记住

工作量随规模按 1.5 次幂增长,生产率随交互密度与任务复杂度相差近十倍,而一半的工作周会被非技术事务吃掉——估算时必须把这三件事一起算进去。