书 · 1999-10 · 安德鲁·亨特(Andrew Hunt)、大卫·托马斯(David Thomas)

《程序员修炼之道》

计算机与 AI书单第 5 名

一句话结论

这本书不教你某门语言,教的是一套做事习惯:对自己写的东西负责、让每条知识只存在一处、小步前进、出错就立刻暴露,这样代码才不会越改越烂。

值不值得读完整本?

值得读完,但不必逐页读。这是一本由 70 来个短小话题组成的经验集,核心思想集中在前两章和第 4、6 章,其余可按需翻阅。1999 年版里关于具体工具的部分已经过时,原则性的部分到今天依然站得住。

  • 先读第 1 章(务实的哲学)和第 2 章(务实的途径)。破窗户、够好就行、DRY、正交性、可逆性、曳光弹都在这两章,是全书骨架,也是考试最该覆盖的部分。
  • 接着读第 4 章(务实的偏执,讲契约式设计和断言)和第 6 章(编码时,讲不凭巧合编程、重构、易于测试的代码),这两章是把前两章的原则落到日常写代码上。
  • 第 3 章(基本工具)讲 1999 年的编辑器、版本控制和脚本,具体工具可以快速浏览,只看“为什么要掌握一套趁手工具”的理由。
  • 第 5 章(解耦、元编程)和第 7、8 章(项目前的需求、团队与自动化)按自己的角色挑着读,做需求或带团队的人收获更大。
  • 每个话题后附有练习和挑战,挑几道做就够了,不需要全做。如果想读更新的版本,可以找 2019 年的 20 周年纪念版(第二版),它重写了不少内容,并补充了现代做法。

核心观点

1. 别留破窗户:软件会腐烂

代码一旦出现没人修的坏设计、错误决定或烂代码,整个系统就会加速变坏。所以发现“破窗户”要立刻修,来不及修就先用木板钉上,比如加注释、标记、临时封装。

为什么:作者借用“废弃大楼里一扇破窗不修,很快就被涂鸦、被砸更多窗”的比喻。破窗会向所有人发出“这里没人在乎”的信号,于是大家觉得再多加一处 hack 也无所谓,整洁的标准在团队心里先垮掉,代码质量随后下滑。这是个自我强化的循环:烂代码越多,越没人愿意认真对待,改动的风险也越大。反过来,一个干净的代码库会让人不好意思成为第一个弄脏它的人。所以作者认为,在腐烂的早期花一点小代价去修,比事后整体抢救便宜得多。

2. DRY:每条知识只在系统里有一个权威表示

“不要重复你自己”:系统里的每一项知识,都应该有单一、无歧义、权威的表示,而不只是不要复制粘贴代码。

为什么:作者认为重复是维护噩梦的根源。同一条知识(比如一个数据结构、一条业务规则)散落在几个地方,一旦要改,你必须记得改全部地方;人总会漏掉一处,于是各处开始不一致,bug 就出在这些不一致里。重复也不只出现在代码里,还包括注释和代码重复、文档和实现重复、数据库 schema 和类定义重复。作者区分了几种成因,有强加的(环境逼你重复)、无意的(没意识到)、图省事的和多人之间不知道对方已经写过的。他的解法是从单一来源生成其他表示,比如用代码生成器从 schema 生成类,让“改一处、处处更新”成为机制,而不是靠自觉。

3. 正交性与可逆性:让改动只影响一处,别把决定做死

互不相关的东西应该互不影响(正交),任何决定都不应该被当成最终决定(可逆)。

为什么:在非正交的系统里,改一个地方会波及许多地方,改动成本、测试成本和出错风险都会成倍放大。正交的系统则能把变化和风险限制在局部,便于复用,也让团队可以并行工作。作者的第二个判断是需求、厂商、平台、技术选择都会变,所以没有什么决定是真正“最终”的。把关键选择(用哪家数据库、哪种架构)藏在抽象层后面,将来就能低成本更换,不必推倒重来。两者的共同逻辑是:世界会变,设计的目标是降低变化的代价,而不是赌自己一次猜对。

4. 曳光弹:先打通一条端到端的细线,再逐步填肉

面对不确定的需求和环境,先写出贯穿所有层的最小可运行骨架,边用边校准,这就是“曳光弹”开发。它和一次性的原型不同,曳光弹的代码会保留下来,是最终系统的骨架。

为什么:作者的出发点是:大多数项目开始时,需求没有定清,技术细节也未知,提前精确计算“该往哪打”几乎不可能。曳光弹把系统从界面到数据库先串通,让你和用户立刻看到“有没有打中”,然后根据反馈微调方向,这样失误暴露得早、纠正得便宜。它还给团队提供了一个可见的集成点和演示成果,让项目早点有“真东西”可看。与原型不同,原型是为了探索一个具体风险而做的一次性实验,做完就扔,而曳光弹是认真写、精简但完整的真实代码。

5. 契约式设计与“早崩溃”:出了不可能的事就立刻停下来

用前置条件、后置条件和不变式把“调用方负责什么、被调用方保证什么”写清楚;代码发现“这不可能发生”的情况时,应当立刻报错终止,而不是带着坏状态继续。

为什么:作者的推理是:程序一旦进入意料之外的状态还继续运行,错误会被一层层传下去,最终在远离根源的地方以怪异的方式爆发,调试极其困难,还可能写坏数据。如果在问题第一次出现时就停下,错误位置和上下文都最清楚,也不会造成连带损害。契约把责任分清,就不需要每个函数对所有输入都做“万一”防御,既减少冗余检查,也让文档与实现保持一致。所以作者认为,“死掉的程序造成的伤害,远小于一个带着错误继续运行的残废程序”。契约式设计这个概念来自 Bertrand Meyer 的 Eiffel 语言。

6. 够好就行的软件,并把质量交给用户一起谈

软件质量是需求的一部分,该做到多好应当和用户商量决定;追求无止境的完美本身也是一种失败。

为什么:作者的推理是:时间和预算有限,永远打磨会错过上线的窗口,而用户往往更需要今天就能用的好软件,而不是明天才有的完美软件。过度打磨还会吃掉可以用来做其他事情的时间。关键是“够好”不是“随便”,质量目标要明确,并在与用户讨论后确定,用户参与也让他们更早用上、更早给反馈。当用户说“够了”的时候就收手,既尊重了用户的优先级,也避免了为不被需要的完美付出代价。这也和前面的“别留破窗户”并不矛盾:够好是对功能和打磨程度的取舍,而不是容忍烂代码。

反对意见

过早抽象比重复更糟,DRY 容易被用过头(Sandi Metz)

Sandi Metz 在 2016 年的文章《The Wrong Abstraction》里提出,工程师为了去重过早提取公共代码,后来需求变化,就不断给这个抽象加参数和分支,最后变成没人敢动的混乱代码。她的结论是“重复远比错误的抽象便宜”,抽象错了就应该把它内联回去重新来过。需要说明的是,这篇文章没有点名批评本书,它针对的是把 DRY 理解成“看到相似代码就合并”的做法。

“破窗户”的类比缺乏实证支持(Bernard Harcourt)

本书用破窗户说明小问题会引发大崩坏,这个说法借自犯罪学的“破窗理论”。Bernard Harcourt 在《Illusion of Order》(2001)里指出,破窗理论从未被实证验证,现有数据甚至显示它是错的。这并不直接否定“代码需要持续维护”,但提醒读者这是个好记的类比,并不是被证明过的因果规律。

靠经验和技巧写程序不够,应该走形式化证明(Edsger Dijkstra)

Dijkstra 在 1988 年的《On the Cruelty of Really Teaching Computing Science》(EWD1036)里主张,编程应当被当作数学的分支,程序的正确性应当靠形式化证明保证,并且尖锐地批评“软件工程”是“注定失败的学科”。按这种立场,本书这种靠经验、习惯和类比的“务实”路线是在治标,而不是从根本上保证正确。

相关推荐

导读由 AI 根据公开资料整理,没有读取原书全文,一切以原著为准。导读 Primer 首页