读前:这一章要回答什么

写软件真正的瓶颈是什么?一本讲"设计哲学"的书,能用什么方式帮你突破它?

第 1 章很短,没有代码,也没有正式提出任何原则。但它定下了全书的基调:作者把软件设计的全部问题收束到一个词上,复杂性(complexity)。后面每一章,无论讲模块、异常、注释还是命名,最终都要回到同一个问题:这样做让系统更容易理解和修改了吗?

这一章还交代了两件对你读书方式影响很大的事:一是作者认为设计不是项目开头的一个阶段,而是贯穿软件一生的持续活动;二是这本书该怎么用,作者的答案是配合代码评审、学会认红旗。本站的训练场就是照着这个思路搭的。

  1. 第 1 章开头讲"两条路"的段落:作者给对抗复杂性分了两类办法。读完想一想,你最近一次"把代码改简单了",属于哪一类?
  2. 第 1 章开头关于瀑布模型和增量开发的段落:注意作者从"软件可以中途大改"推出了一个对个人很具体的要求,读的时候把它找出来(提示:和你的时间分配有关)。
  3. 1.1 节:作者对"怎么用这本书"给了三条建议:配合代码评审、学认红旗、保持适度。第三条会在后面多章的 "Taking it too far" 小节反复出现。

翻开书之前,你大概先看到了封面:上半部分是一大团蓝色、绿色的线,绕来绕去缠成一堆;中间一条黑色横带写着书名;横带下面,同样的线一根根垂直落下,互不交叉。书里没有解释这幅图。

以下不在书里,来自 Ousterhout 在 X(原 Twitter)上的回帖。2026 年有读者直接问这幅图是什么意思,Ousterhout 回答(2026-01):那些线代表"意大利面代码"。横带上方是一团乱;应用了好的软件设计方法之后,也就是黑色横带下方,它变得更简单、更干净。书名所在的那条横带,就是"设计"起作用的地方。

更早还有一次。2018 年书刚出版时,一位读者晒出新到的书,开玩笑说:看来我们都该努力写出封面这样的代码?Ousterhout 回了一句:不如努力写得像封面的下半部分(2018-05)。

本站补充:留意一个细节,上下看上去是同一批线,只是不再缠在一起。这和第 2 章的说法是一致的:好的设计不一定让系统做的事变少,它消掉的是线与线之间的纠缠,也就是依赖。

核心观点

瓶颈是"理解"

编程几乎不受物理定律约束,真正限制我们的是能否理解自己造出来的系统。复杂性随系统演化不可避免地累积,开发因此变慢、出错。

两条路:消除与封装

消除:让代码更简单、更显然(去掉特殊情况、一致地命名)。封装:模块化,让人一次只面对一部分复杂性。

工具有上限

好工具能帮忙,但只靠工具走不远;要想用更低的成本造更大的系统,得让软件本身更简单。

原则不是菜谱

书里的原则近乎哲学,不会直接告诉你最好的设计是什么,但能帮你比较几种方案、引导你探索设计空间。

适度

每条规则都有例外,任何想法推到极端都会出问题。好的设计是在相互竞争的想法之间取得平衡。

设计永远没有做完

软件可以中途大改,所以增量开发行得通;也正因如此,设计是持续的,每个开发者都该拿出一部分时间改进设计。

红旗 + 代码评审

红旗是"这里可能过于复杂"的迹象。看别人的代码更容易发现问题;看到红旗就停下来找替代设计,多试几种。

The greatest limitation in writing software is our ability to understand the systems we are creating.— 第 1 章开头

心智模型:两条路,和这本书的地图

先把作者给的两类办法画出来。左边是"消除":复杂性直接没了;右边是"封装":复杂性还在,但被关进了模块,大多数人大多数时候看不见它。书的主体(第 4~11 章)大多在讲第二条路怎么走好,但第一条路贯穿全书。下图把各章大致归了类,这是本站的整理,不是原书的划分,不少章节两边都沾。

系统的复杂性 消除:让它不存在 代码更简单、更显然 · 去掉特殊情况(第 6、10 章) · 名字精确、一致(第 14、17 章) · 让代码一目了然(第 18 章) 结果:要理解的东西变少了 封装:让它待在别处 模块化设计 · 深模块、信息隐藏(第 4、5 章) · 通用、分层、往下拉(第 6~8 章) · 接口注释(第 12、13 章) 结果:一次只需面对一小部分
对抗复杂性的两条路。章节归类为本站整理,仅供导航。

下面用一个小例子把两条路走一遍:一个网络服务里的收包队列,生产者往里放包,满了就丢弃并计数。

// 网络线程(为简化,假设单线程)
if (count == kCap) {
    dropped++;
} else {
    ring[tail] = pkt;
    tail++;
    if (tail == kCap) tail = 0;   // 回绕:特殊情况
    count++;                      // count、head、tail 三者要保持一致
}
// 工作线程里还有一段对称的 head 处理,同样有回绕判断

调用方直接摆弄 head、tail、count 三个状态,还要处理回绕。每个碰这段代码的人都得把这些全部弄懂。

// head、tail 只增不减(uint64_t,实际不会溢出),
// 元素个数就是 tail - head,下标取模即可,不再需要 count 和回绕判断。
if (tail - head == kCap) {
    dropped++;
} else {
    ring[tail++ % kCap] = pkt;
}

回绕这个特殊情况消失了,需要保持一致的状态从三个减到两个。代价:读者得理解"只增不减的下标"这个约定,所以那两行注释不能省。

// 固定容量的包队列。满时 tryPush 返回 false,空时 tryPop 返回 nullopt。
class PacketQueue {
public:
    bool tryPush(const Packet& p);
    std::optional<Packet> tryPop();
};

if (!queue.tryPush(pkt)) dropped++;

下标、取模、容量判断全藏进了 PacketQueue,调用方只剩一行。复杂性没有消失,但只有维护这个类的人需要面对它。

两条路不是二选一:第三个面板的实现里,用的正是第二个面板的写法。先尽量消除,消除不了的再封装。至于"封装进一个类"什么时候值得、什么时候只是多了一层,第 4 章会给出判断标准。

这本书怎么读,本站怎么用

作者坦白说,书里的原则偏抽象,又很难找到"小到能放进书里、又大到能体现真实问题"的例子,所以光读书可能不够。作者的建议是把书和代码评审结合起来:读别人代码的时候,想想它是否符合书里的概念、和代码的复杂程度有什么关系。

本站按这个思路分成三层,建议每章按顺序走一遍:

① 章节导读 读前带着问题 读后用自测巩固 ② 训练场 大量原创代码题 练"认得出问题" ③ 项目 minikv 在真实代码里重构 练"改得出更好的" 原书是主角:本站每一层都假设你读过对应章节
本站的三层结构。
层做什么对应作者的哪条建议
章节导读(就是这种页面)读原书前看"读前"和"读原书时留意";读完回来看核心观点、边界与反方,做自测题。理解概念,并且知道每条原则的边界(适度)。
训练场读代码、认红旗、在两种设计里选一种、先写再对照参考答案。答题记录进入间隔复习。学会认红旗;像做代码评审一样读别人的代码。
项目一个本站原创的 C++ 迷你键值存储,故意按"先跑起来再说"的风格写成。从第 4 章起,每隔几章有一个重构任务。看到红旗就找替代设计,多试几种,在真实代码里体会取舍。

每一章建议这样走

  1. 读前(5 分钟):看本站这一章的"读前"和"读原书时留意",记住那三个问题。
  2. 读原书:带着问题读。本站不翻译、不复述原书,很多关键论证只在原书里。
  3. 读后(15 分钟):回来看核心观点、原创例子和"边界与反方",做自测题。自测错了,回原书找对应小节。
  4. 训练:去训练场做本章的题。答错的题 10 分钟后会再出现,答对的按间隔复习逐渐拉长间隔。
  5. 动手:有项目任务的章节,在 minikv 上做对应的重构;最后在页面底部写下"读后一句话"。

两个常见的误用。一是把红旗当成检查清单逐条打勾:作者说红旗是代码可能过于复杂的迹象,它提示你去找替代设计,而不是直接判死刑。本站补充:找了一圈发现原设计就是最好的,也是正常结果。二是"读完就觉得懂了":这本书的观点很少让人反对,真正难的是在自己的代码里、在赶工期的时候认出问题。所以本站把大部分篇幅给了训练题。

如果你读过 OSTEP,你已经见过"封装复杂性"走到极致的样子:进程抽象把 CPU 调度藏起来(OSTEP 第 4 章),地址空间把分页、TLB、换页藏起来。应用程序员几乎从不需要想这些,而操作系统内部的复杂性丝毫不少。用第 2 章的话说,这些复杂性被关在了几乎没人需要去看的地方。

另外还有速查页(全部红旗和原则,各配一个小例子)和几篇专题,例如APOSD 与 Clean Code 的分歧。

红旗与代码评审

作者认为,提高设计能力最好的办法之一是学会认红旗(red flag):一段代码可能比它需要的更复杂的迹象。书中会在各章陆续指出红旗,书末汇总了最重要的十几个。第 1~3 章还没有正式的红旗,第 4 章的"浅模块"是第一个。

When you see a red flag, stop and look for an alternate design that eliminates the problem.— 第 1.1 节

这句话里有两个容易忽略的要求。第一是停下来:红旗出现时你通常正忙着让功能跑起来,最自然的反应是"先这样吧"。第二是找替代设计,而不是在原设计上打补丁。作者还提醒,刚开始可能要试好几种方案才能消掉一个红旗,不要轻易放弃,试得越多学得越多;久而久之你的代码红旗会越来越少,你也会自己发现新的红旗。

为什么要配合代码评审?作者给的理由很朴素:看别人代码里的设计问题,比看自己的容易;评审还能让你见到新的设计思路和编程技巧。本站补充一个写评审意见的小模板,你可以在训练场的题目里反复练:

位置:文件和行号
现象:看到了什么(哪个红旗 / 哪种复杂性症状)
后果:以后谁、在什么情况下会因此多花力气或出错
替代:至少一种更好的设计(最好两种,并说明你的倾向)

这四行里最重要的是"后果":它把"我觉得不好看"变成"它会让以后的修改变难",这正是第 2 章给复杂性下的定义。

边界与反方

作者在 1.1 节明确说,任何设计想法推到极端都会出问题,并专门在几章里写了 "Taking it too far" 小节(如 5.9、8.3、10.9、17.3 节)。读这些小节和读原则本身一样重要。本站每章的"边界与反方"一节就是为此而设。

作者在前言里把这本书称为一本观点性的书(opinion piece),建议读者对书中的建议保持一点怀疑,并说降低复杂性这个总目标比书里任何一条具体原则都重要:如果某个想法在你那里并没有降低复杂性,就不必坚持用它。本站会在适当的地方标出作者观点与其他流派(例如 Clean Code)的冲突,但不替你下结论。

作者支持增量开发,理由是瀑布模型做不到一开始就想清楚大型系统,问题暴露时又只能打补丁。但增量的推论是设计永远在进行,而不是"边做边看、不必设计"。第 19.2 节会补上另一半:敏捷开发很容易让人只盯着功能,滑向战术式编程(第 3 章的主题)。

作者在开头承认好工具帮了大忙,但认为只靠工具有上限,必须让软件本身更简单。本站补充:写代码越来越快的工具,并不会减少需要被理解的东西;如果用它更快地堆出复杂性,反而会加速第 3 章说的战术式编程。专题AI 时代读这本书会展开讨论。

以下不在书里,来自书中给出的读者讨论组。2025 年有位教师问怎样教这本书的内容。Ousterhout 的回答对自学的人同样适用(2025-03):除非真的在写系统、亲身体会到设计选择的后果,否则很难学会。在 Ousterhout 的课上,学生"开窍"的时刻是交了第一个不算小的项目、拿到代码评审之后:评审指出哪里太复杂,说明这份复杂是违反了哪条原则造成的,再演示照着原则改完之后简单了多少。没有自己写的代码里的那些细节,也没有重写的机会,原则是体会不到的。伪代码不行,因为只有代码真的要编译、要运行时,那些细小的依赖才会冒出来。Ousterhout 说很希望有不必大量写代码就能教设计的办法,但还没找到。

这也是本站把重心放在训练场和重构项目上的原因。训练题只能练"认出来",替代不了"自己写、被人指出、再重写";所以项目里每个任务都请先自己做完,再对照参考解答。

同一个月,有读者把复杂性比作熵。Ousterhout 说自己常讲一句玩笑式的"软件热力学第一定律":软件系统总是朝复杂性增加的方向演化;复杂性一旦进来,就极难再降下去(2025-03,不在书里)。针对"只要随手保持代码整洁,好设计自然会浮现"的说法,Ousterhout 表示难以相信:整洁是局部的,找到合适的分解需要全局地想;有了设计之后,才能在各个部分上局部、独立地干活。这是对上面"增量开发 ≠ 不需要设计"的又一种说法。

练一练

第 1~3 章还没有红旗,题目以判断和设计选择为主。先自己判断,再看解析。

自测题

作者认为,写软件最大的限制来自哪里?
编程几乎不受物理定律约束,只要能想清楚就能写出来,所以限制在于"想得清楚"。系统越大、参与的人越多,复杂性越难管理。工具有帮助,但作者认为只靠工具有上限。
下面哪一项属于"封装复杂性"而不是"消除复杂性"?
A、B、D 三项让复杂性直接消失;C 的重连逻辑依然存在,只是被关进了一个模块,调用方不必再理解它。这就是模块化设计。
作者认为瀑布模型不适合软件的根本原因是什么?
D 是常见的误读:软件可以中途改,这让增量开发成为可能,但作者由此推出的是"设计要持续进行",而不是"不需要设计"。
作者说书中的原则(如"类应该深")"近乎哲学"。按作者的说法,它们应该怎么用?
作者明说没有保证出好设计的简单配方;原则不一定能直接指出最好的设计,但能帮你比较方案。它们也适用于 C 函数、子系统、网络服务等,并且每条都有边界。
按作者的建议,为什么这本书最好配合代码评审来用?
书里的例子受篇幅限制,很难完全体现真实系统的问题;代码评审提供了真实的代码,而且"旁观者清"。这也是本站训练场的出发点。
关于书中反复出现的 "Taking it too far" 小节,下列哪种理解最符合作者的意图?
作者在 1.1 节说,每条规则都有例外,任何想法推到极端都会出问题。这些小节不是推翻原则,而是标出原则的适用范围,是培养判断力最有用的部分之一。

小结与读后一句话

下一章回答本章留下的问题:复杂性到底是什么?作者会给出一个实用的定义、三种症状和两个成因,后面全书都用这套词汇说话。

读后一句话:用你自己的话写下这一章最重要的一个观点,最好结合你的工作。