第 5 章 · 画蛇添足
题记是奥维德的一句:“聚沙成塔,集腋成裘。“用在功能上,这句话不是褒义——每一小勺都合理,堆起来就是一个没人能用得动的庞然大物。
这一章讲了什么
第一章提出的问题在这里被接着问下去:既然把制定功能规格说明的责任从"快速、低成本地开发产品"的责任中分离了出去,那么用什么准则约束结构师的创造热情?作者给出一个总体回答——结构师与实现人员之间彻底、谨慎、和谐的交流——然后展开两个部分。
第一部分是"结构师的交互准则和机制”。他先拿建筑业打比方:结构设计师用估算技术编预算,承包商的报价总会超过预算,设计师据此修正估算或修订设计,或者建议更省钱的实现方式。计算机系统的结构师处在类似循环里,优势是在设计早期就能拿到"报价”,几乎问了就有答案;劣势是常常只剩一个承包商,而后者可以用抬高或压低估计来表达对设计的好恶。所以尽早交流、持续沟通的价值是双向的:结构师获得成本意识,开发人员获得对设计的信心,而各自的责任分工不被混淆。
当估算过高时,结构师只有两条路:削减设计,或者改用成本更低的实现方法。后一条是在挑战开发人员的做事方式,要成功必须守住四条准则:
- 牢记实现的责任由开发人员承担,结构师只能建议,不能支配;
- 为指定的说明主动建议一种实现方法,同时准备接受任何其他能达到目标的方法;
- 对这种建议保持低调、不公开;
- 准备放弃自己坚持的改进建议。
作者的提醒很关键:一般开发人员会反对体系结构上的修改建议,而且通常他是对的——因为在实现层面,某些次要特性的改动会带来意料之外的开销。
第二部分"自律——开发第二个系统所带来的后果"是本章著名的诊断。第一个系统里,结构师知道自己对任务了解不够,所以谨慎、仔细,倾向于精炼和简洁;那些冒出来的装饰与润色功能,被他搁置在一边,标记为"下一个"项目的内容。第一个系统结束之后,他信心十足、精通这类系统,于是着手第二个——而第二个系统是设计师们所设计的最危险的系统。普遍倾向是过分设计:把第一个系统里小心搁置的想法全部加进去。到第三、第四个系统时,先前经验互相验证,才会形成对通用特性的判断。
书里给的例子一个比一个沉重。IBM 709 是对成功的 704 的二次开发,操作集合设计得如此丰富,只有一半操作被客户经常使用。更严重的是 Stretch 计算机:它是很多人被压抑的创造力宣泄口,也是他们中大多数人的第二个系统,Strachey 评审时的评价是——“它非常富有创造性,设计非常复杂,却非常高效。但不知为什么,我同时感觉到,它粗糙、浪费、缺乏优雅,并让人觉得必定存在某种更好的方法可以代替它”。
OS/360 对大多数设计者来说是第二个系统:团队成员来自 1410-7010 磁盘操作系统、Stretch 操作系统、Mercury 实时系统和 7090 的 IBSYS,几乎没有人有两次以上的操作系统经验。所以作者称它是"软件行业的 Stretch 系统",Strachey 的赞誉和批评可以一字不改地套用。随后是一串具体病例:
| 病例 | 问题所在 |
|---|---|
| 26 字节常驻日期翻转例程 | 为正确处理闰年的 12 月 31 日写了常驻代码,其实完全可以留给操作员 |
| 链接编辑器的覆盖(overlay)管理 | 静态覆盖技术的顶峰,但系统已经是多道程序、动态内核分配,概念上正相冲突;它还占空间、拖慢链接,讽刺的是链接的目的本是避免重新编译——“像一个挺着大肚子的节食者” |
| TESTRAN 批调试程序 | 快照与转储功能优雅,但"无需重编译的批调试"整个概念已被交互式系统与快速编译挑战 |
| OS/360 调度程序 | 管理固定批作业非常杰出,对远程任务项、多道程序与永久驻留交互式子系统却几乎毫无帮助,实际上让它们更难 |
这里有一个容易被忽略的观察:第二系统的表现不只是"加功能",还有一种更隐蔽的形式——对某些已经被淘汰的技术做细化与精炼。作者的判断很狠:“直到系统的思想已经十分优越时,才开始对原有技术进行细化和精炼。”
那么怎么避免?结构师跳不过第二个系统,但可以有意识地警惕它,运用特别的自我约束准则,并根据系统基本理念与目的的变更舍弃一些功能。他给出一个很实在的准则:为每个小功能分配一个值——“每次改进,功能 x 不超过 m 字节的内存和 n 微秒”。这些数字在一开始充当决策向导,在物理实现期间成为指南和对所有人的警示。至于项目经理,作者的要求是确保由至少有两次以上系统经验的资深结构师来做设计,同时保持对特殊诱惑的警觉,不断提出正确的问题,确保原则与目标在详细设计中得到完整体现。
我的判断
- 这是全书最容易对号入座的一章。它揭示的其实是学习曲线上的一个陷阱:第一次因为无知而谨慎,第二次因为过度自信而放纵。第一次的克制不是修养,而是不安全感;所以当不安全感消失时,克制也随之消失。
- “为每个小功能分配 m 字节、n 微秒"是这一章最可操作的处方。它比"要克制"“要有品味"这类劝告硬得多——把审美问题转成了预算问题,而预算是可以被质询、被拒绝的。这条我认为可以直接搬出软件范围使用。
- 我怀疑"第二系统"这个框架的边界。作者的样本全是超大型系统,且发生在"一个设计师一辈子做两三个系统"的年代。今天一个技术负责人一年可能做五个系统,第二系统效应会被高频迭代稀释;但它也可能以更隐蔽的形式出现——每一次技术栈迁移或重写,都是一次"我终于可以把它做对"的放纵机会。
- 那些病例的共同结构是:局部最优的技术精炼,撞上已经改变的系统基本假设。所以它给了我一条可复用的检查动作——评估任何改进之前,先问"它依赖的基本假设现在是否还成立”,而不是问"它本身好不好”。
- 这章末段的中译有一处歧义:“他必须坚持至少拥有两个系统以上开发经验结构师的决定”。对照上下文,我理解为"坚持让有两次以上系统经验的资深结构师来做设计";若按字面读成"服从结构师的决定",就会与上一段"根据基本理念及目的变更,舍弃一些功能"冲突。读书时遇到这种句子,回看原页确认是值得的。
- 最难得的是那句"开发人员反对结构修改建议,通常他是对的"。它等于要求结构师主动接受自己的建议被否——这在一个以品味和远见为荣的角色上,是很高的自律要求。
和我手上的工作有什么关系
- 我手上的东西很多本身就是"第二系统":这个站点是对 OINK Starter 的二次开发,而 Starter 是别人的第一次。区别在于我有一份明确的约束清单——
AGENTS.md的硬性规则、构建门禁、.gitignore的排除范围——它们实际承担了"为每个功能分配预算"的角色。没有这些约束,二次开发膨胀成"什么都想要"是必然的。 - 顺着这一章,我打算给主题定制立一条可执行的规矩:任何视觉改动必须写明它替换掉了什么,不允许净增。这就是"m 字节、n 微秒"在内容站上的等价物。
- “局部最优撞上已变假设"那组例子,几乎是为我的技术栈迁移史写的:花力气优化过的东西,常在半年后因为架构变化而作废。以后决定优化前,先问一句这条优化依赖的假设还在不在。
- “结构师只能建议不能支配"对我和 Agent 的协作有直接含义:概念与结构的决定权在我,具体实现的选择权应当交还给执行者。反过来,当我为某个实现方案辩护时,要检查自己是不是已经越界到实现层了。
一句话记住
第二个系统最危险:第一次因无知而谨慎,第二次因自信而放纵,把当初搁置的修饰一股脑加上去——可靠的解法不是提醒自己谦虚,而是给每个功能一个明确的预算。