<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>软件工程 on 方糖猫Blog</title>
    <link>https://ftmao.com/blog/shelves/%E8%BD%AF%E4%BB%B6%E5%B7%A5%E7%A8%8B/</link>
    <description>AI 视频、AI Agent 与工程实践笔记</description>
    <generator>Hugo</generator>
    <language>zh-CN</language>
    <lastBuildDate>Sat, 26 Oct 2024 14:00:00 +0800</lastBuildDate><atom:link href="https://ftmao.com/blog/shelves/%E8%BD%AF%E4%BB%B6%E5%B7%A5%E7%A8%8B/index.xml" rel="self" type="application/rss+xml" /><item>
      <title>《架构整洁之道》详解：从编程范式到整洁架构的完整指南</title>
      <link>https://ftmao.com/blog/bookstore/clean-architecture-book-notes/</link>
      <pubDate>Sat, 26 Oct 2024 14:00:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/bookstore/clean-architecture-book-notes/</guid>
      <description>&lt;h1 id=&#34;架构整洁之道详解从编程范式到整洁架构的完整指南&#34;&gt;《架构整洁之道》详解：从编程范式到整洁架构的完整指南&lt;/h1&gt;
&lt;p&gt;《架构整洁之道》（Clean Architecture: A Craftsman&amp;rsquo;s Guide to Software Structure and Design）是 Robert C. Martin（业内昵称&amp;quot;Bob 大叔&amp;quot;）在 2017 年出版的一本书。它是《代码整洁之道》《敏捷软件开发》之后，Bob 大叔对自己半个世纪编程经验的又一次系统总结。&lt;/p&gt;
&lt;p&gt;这本书要回答的核心问题只有一个：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;如何设计一个系统，让它活得久、改得动、换得起？&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;本文面向&lt;strong&gt;从未接触过软件架构概念&lt;/strong&gt;的读者，按照全书六个部分的结构逐层展开，配合图示和实例，帮你建立完整的知识地图。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;第一部分-概述架构到底是什么&#34;&gt;第一部分 概述：架构到底是什么&lt;/h2&gt;
&lt;h3 id=&#34;1-设计与架构没有本质区别&#34;&gt;1. 设计与架构没有本质区别&lt;/h3&gt;
&lt;p&gt;很多人以为&amp;quot;架构&amp;quot;是宏观的大图，&amp;ldquo;设计&amp;quot;是微观的细节。Bob 大叔认为两者其实是一回事：&lt;strong&gt;架构就是系统设计，系统设计就是架构&lt;/strong&gt;。它们只是在不同的观察尺度上，不存在一条清晰的分界线，两者共同决定了系统的整体形状。&lt;/p&gt;
&lt;p&gt;一个好的架构目标可以用一句话概括：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;用最小的人力成本来满足构建和维护该系统的需求。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;衡量标准很朴素：如果每次新增需求所需的成本都维持在较低水平，不会因为系统变老而持续上升，那这个架构就是好架构。反之，如果每改一个功能都要牵动全身、修复一个 bug 引入三个新 bug，那架构就已经坏掉了。&lt;/p&gt;
&lt;h3 id=&#34;2-两个价值维度行为与架构&#34;&gt;2. 两个价值维度：行为与架构&lt;/h3&gt;
&lt;p&gt;每个软件系统都为利益相关者提供两种不同的价值：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;行为价值（Behavior）&lt;/strong&gt;：系统按需求文档干活，帮用户赚钱或省钱。这是程序员最关注的部分。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;架构价值（Architecture）&lt;/strong&gt;：软件之所以叫&amp;quot;软&amp;quot;件，就在于它&lt;strong&gt;应该是容易被修改的&lt;/strong&gt;。如果需求变了而系统改不动，这个系统就失败了。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这两者的优先级，可以用艾森豪威尔矩阵（重要/紧急四象限）来思考：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;&lt;/th&gt;
					&lt;th&gt;紧急&lt;/th&gt;
					&lt;th&gt;不紧急&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;重要&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;① 行为（功能）&lt;/td&gt;
					&lt;td&gt;② &lt;strong&gt;架构&lt;/strong&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;不重要&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;③ 界面细节等&lt;/td&gt;
					&lt;td&gt;④ 杂项&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;常见的悲剧是：业务经理永远把&amp;quot;紧急&amp;quot;排在前面，于是团队不断牺牲&amp;quot;重要但不紧急&amp;quot;的架构，最终系统腐化到无法维护。&lt;strong&gt;为好的架构而斗争，是软件架构师的职责&lt;/strong&gt;——因为架构师本身就是利益相关者之一。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;第二部分-从基础构件开始编程范式&#34;&gt;第二部分 从基础构件开始：编程范式&lt;/h2&gt;
&lt;p&gt;这一部分回顾了软件历史上仅有的三种编程范式。一个很有意思的观点是：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这三种范式都是 1958–1968 这十年间被发明的，之后的几十年里，&lt;strong&gt;再也没有出现过新的编程范式&lt;/strong&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;三种范式它们都在限制我们&#34;&gt;三种范式：它们都在&amp;quot;限制&amp;quot;我们&lt;/h3&gt;
&lt;p&gt;三种范式有一个共性：它们都&lt;strong&gt;剥夺&lt;/strong&gt;了程序员的某种能力，而不是赋予新能力。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    A[结构化编程&amp;lt;br/&amp;gt;1968 Dijkstra] --&amp;gt;|限制了| A1[goto 的无约束跳转]
    B[面向对象编程&amp;lt;br/&amp;gt;1966 Simula] --&amp;gt;|限制了| B1[函数指针的滥用&amp;lt;br/&amp;gt;通过多态接管间接调用]
    C[函数式编程&amp;lt;br/&amp;gt;1958 λ演算/Lisp] --&amp;gt;|限制了| C1[赋值语句&amp;lt;br/&amp;gt;变量不可变性]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;结构化编程&lt;/strong&gt;（Structured Programming）：用顺序、选择、循环三种结构取代了自由的 &lt;code&gt;goto&lt;/code&gt; 跳转。Dijkstra 的名言是&amp;quot;goto 是有害的&amp;rdquo;。这一范式还带来一个深远影响：既然无法数学式地&amp;quot;证明&amp;quot;程序正确，那就通过&lt;strong&gt;测试&lt;/strong&gt;来&amp;quot;证伪&amp;quot;——结构化编程催生了测试驱动开发（TDD）的思想土壤。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;面向对象编程&lt;/strong&gt;（OOP）：把数据和函数打包成对象。关于 OOP 的三大特性，书里有独到见解：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;封装&lt;/strong&gt;：其实 C 语言用头文件隐藏实现做得更好，OOP 反而弱化了它；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;继承&lt;/strong&gt;：C 程序员早就手动模拟过，OOP 只是让它更方便；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;多态&lt;/strong&gt;：这才是 OOP 真正的核心——它让&lt;strong&gt;源码依赖方向可以被反转&lt;/strong&gt;。调用方通过接口调用实现方，源码依赖与控制流方向相反，这就是全书后面反复强调的&amp;quot;依赖反转&amp;quot;的武器。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;函数式编程&lt;/strong&gt;（Functional Programming）：核心思想是&lt;strong&gt;变量不可变&lt;/strong&gt;（immutability）。没有赋值，就没有竞态条件、死锁、并发更新问题——所有并发 bug 都源于可变状态。现实中的妥协方案是&amp;quot;事件溯源&amp;quot;（Event Sourcing）：只存事务日志，不存状态，需要状态时用日志重算，从而消灭对可变状态的依赖。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;给初学者的 takeaway&lt;/strong&gt;：软件架构的底层构件只有三种——顺序、分支、循环；而范式教会我们的是&amp;quot;&lt;strong&gt;不要做什么&lt;/strong&gt;&amp;quot;，架构师要在这些限制内搭建系统。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;第三部分-设计原则solid&#34;&gt;第三部分 设计原则：SOLID&lt;/h2&gt;
&lt;p&gt;SOLID 是函数/类级别（中层）的五个设计原则，目标是让软件模块：&lt;strong&gt;可扩展、易修改、易理解、可复用&lt;/strong&gt;。这部分是全书中读者基础最扎实的部分，但书里对每条原则的理解都比市面上的通俗版本更精确。&lt;/p&gt;
&lt;h3 id=&#34;srp-单一职责原则&#34;&gt;SRP 单一职责原则&lt;/h3&gt;
&lt;p&gt;最容易被误解的一条。SRP &lt;strong&gt;不是&lt;/strong&gt;&amp;ldquo;每个函数只做一件事&amp;rdquo;（那是函数层面的小原则），而是：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;一个模块应该只对一类行为者（actor）负责。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;经典例子：一个 &lt;code&gt;Employee&lt;/code&gt; 类同时有 &lt;code&gt;calculatePay()&lt;/code&gt;（CFO 关心）、&lt;code&gt;reportHours()&lt;/code&gt;（COO 关心）、&lt;code&gt;save()&lt;/code&gt;（CTO 关心）三个方法。三个方法服务三类不同的行为者，一旦为 CFO 改的代码意外影响了 COO 的报表，就是 SRP 被破坏的恶果。解法是按行为者拆分。&lt;/p&gt;
&lt;h3 id=&#34;ocp-开闭原则&#34;&gt;OCP 开闭原则&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;对扩展开放，对修改关闭。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;新增行为应该通过增加代码完成，而不是修改旧代码。实现手段是：把系统按&amp;quot;变更原因&amp;quot;分层，高层策略组件不受低层细节变化的影响，层与层之间用接口隔离。&lt;/p&gt;
&lt;h3 id=&#34;lsp-里氏替换原则&#34;&gt;LSP 里氏替换原则&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;任何使用基类的地方，替换成子类后行为依然正确。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;违反 LSP 的著名案例：正方形（Square）继承长方形（Rectangle）——&lt;code&gt;setWidth&lt;/code&gt; 语义在两个类里不一致，导致依赖长方形的代码出问题。在架构层面，LSP 延伸为&amp;quot;接口/服务的可替换性&amp;quot;：REST 接口协议不一致，本质上也是违反 LSP。&lt;/p&gt;
&lt;h3 id=&#34;isp-接口隔离原则&#34;&gt;ISP 接口隔离原则&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;不要强迫使用者依赖它们不需要的方法。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;胖接口会引入不必要的依赖——改动接口中别人不用的部分，也会强制所有使用者重新编译部署。架构层面的教训：&lt;strong&gt;任何层次的&amp;quot;多余依赖&amp;quot;都有害&lt;/strong&gt;，包括对一个打包了过多功能的库或框架的依赖。&lt;/p&gt;
&lt;h3 id=&#34;dip-依赖反转原则&#34;&gt;DIP 依赖反转原则&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;源码依赖方向应与控制流方向相反，高层策略不应依赖低层细节，两者都应依赖抽象。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这是整本书的&amp;quot;元原则&amp;quot;。Java 这类语言中，接口声明属于调用方一侧（内层），实现类属于被调用方一侧（外层），依赖箭头跨过边界指向内层——这就是后面整洁架构能成立的技术基础。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    subgraph 内层[&amp;#34;内层（高层策略）&amp;#34;]
        UC[用例] --&amp;gt;|依赖接口| PORT[&amp;#34;接口（输出端口）&amp;#34;]
    end
    subgraph 外层[&amp;#34;外层（实现细节）&amp;#34;]
        IMPL[Presenter 实现] -.实现.-&amp;gt; PORT
        DB[数据库实现] -.实现.-&amp;gt; PORT2[数据访问接口]
    end
    UC --&amp;gt;|依赖接口| PORT2
    style 内层 fill:#e8f0fe
    style 外层 fill:#fef3e8&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;第四部分-组件构建原则&#34;&gt;第四部分 组件构建原则&lt;/h2&gt;
&lt;p&gt;SOLID 管&amp;quot;类和函数怎么写&amp;quot;，这一部分管&amp;quot;&lt;strong&gt;类和函数怎么打包成组件&lt;/strong&gt;&amp;quot;（组件 = 独立部署的最小单元，如 jar、dll、服务）。&lt;/p&gt;
&lt;h3 id=&#34;组件聚合三原则哪些类该放进一个组件&#34;&gt;组件聚合三原则（哪些类该放进一个组件）&lt;/h3&gt;
&lt;p&gt;三条原则互相拉扯，架构师要做权衡：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;REP 复用/发布等同原则&lt;/strong&gt;：组件的复用粒度等于发布粒度——一个组件里的东西应该作为一个整体被版本化和发布。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CCP 共同闭包原则&lt;/strong&gt;：会因同一原因、同一时间变化的类应该聚在同一个组件里（SRP 在组件层面的放大版）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CRP 共同复用原则&lt;/strong&gt;：不要强迫组件的使用者依赖他们不需要的东西（ISP 在组件层面的放大版）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;CCP 和 CRP 通常是矛盾的：按变更原因聚合容易，但组件可能变胖；按使用方切得细，组件数量又会爆炸。&lt;/p&gt;
&lt;h3 id=&#34;组件耦合三原则组件之间怎么依赖&#34;&gt;组件耦合三原则（组件之间怎么依赖）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ADP 无环依赖原则&lt;/strong&gt;：组件依赖图中不允许出现环。打破环的手段有两个：依赖反转（在中间加接口）或新建一个公共组件。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SDP 稳定依赖原则&lt;/strong&gt;：依赖方向必须指向更稳定的组件。越难修改的组件越稳定，不稳定（易变）的组件应该依赖稳定的组件。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SAP 稳定抽象原则&lt;/strong&gt;：越稳定的组件应该越抽象。所以架构应该是：&lt;strong&gt;核心策略 = 稳定 + 抽象（接口）&lt;/strong&gt;，外围实现 = 不稳定 + 具体。极端的两端是&amp;quot;最稳定且最抽象&amp;quot;（理想）和&amp;quot;最不稳定且最具体&amp;quot;（胶水代码），大部分组件落在两者之间的主序列附近。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;第五部分-软件架构本书的核心&#34;&gt;第五部分 软件架构：本书的核心&lt;/h2&gt;
&lt;h3 id=&#34;1-什么是软件架构&#34;&gt;1. 什么是软件架构&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;架构的实质是：&lt;strong&gt;尽可能长时间地推迟决策，保留尽可能多的可选项。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;一个软件系统的架构质量，取决于它&lt;strong&gt;支撑系统全生命周期&lt;/strong&gt;的能力：开发、部署、运行、维护。好的架构让系统易于理解、易于开发、易于维护、易于部署，其终极目标是&lt;strong&gt;最小化整个生命周期的人力成本&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;架构的次要目标还包括让系统可以按用例独立开发、独立部署、独立运行。&lt;/p&gt;
&lt;h3 id=&#34;2-独立性&#34;&gt;2. 独立性&lt;/h3&gt;
&lt;p&gt;好架构必须支持：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;按用例解耦&lt;/strong&gt;：购物车、订单、用户管理……不同用例之间的代码互不干扰；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;解耦运行方式&lt;/strong&gt;：系统可以跑成单体、SOA、微服务，部署方式是可推迟的决策；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;独立开发与独立部署&lt;/strong&gt;：不同团队可以并行开发不同模块而互不阻塞。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;3-划分边界软件架构就是画线的艺术&#34;&gt;3. 划分边界：软件架构就是画线的艺术&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;架构师的工作是&lt;strong&gt;画出边界线&lt;/strong&gt;，把系统分割成组件。一部分组件实现核心业务逻辑，另一部分组件作为与核心逻辑无关的&amp;quot;插件&amp;quot;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;划线的依据是：&lt;strong&gt;哪些东西变化的原因不同&lt;/strong&gt;。UI、数据库、业务规则、第三方服务，它们变化的理由完全不同，所以它们之间应该画线。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TD
    UI[用户界面] -.插件式依赖.-&amp;gt; CORE
    DB[(数据库)] -.插件式依赖.-&amp;gt; CORE
    WEB[Web 框架] -.插件式依赖.-&amp;gt; CORE
    EXT[外部服务] -.插件式依赖.-&amp;gt; CORE
    subgraph CORE[&amp;#34;核心业务逻辑（不知道外围的存在）&amp;#34;]
        ENT[业务实体]
        UC[用例]
    end
    style CORE fill:#e8f0fe&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;关键点：&lt;strong&gt;核心业务逻辑根本不依赖这些东西，是这些东西反过来依赖核心业务逻辑&lt;/strong&gt;。数据库不是系统的中心，业务规则才是。这种&amp;quot;以核心业务为中心，外设皆插件&amp;quot;的思想，Bob 大叔称之为&lt;strong&gt;插件式架构&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id=&#34;4-边界剖析跨边界的调用怎么发生&#34;&gt;4. 边界剖析：跨边界的调用怎么发生&lt;/h3&gt;
&lt;p&gt;跨越边界的通信分两种方向：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;顺控制流调用&lt;/strong&gt;（外层主动调用内层）：直接调用即可，依赖方向天然向内，没有架构问题；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;逆控制流回调&lt;/strong&gt;（内层需要通知外层）：必须用&lt;strong&gt;依赖反转&lt;/strong&gt;——内层定义接口，外层实现接口，保证源码依赖依然指向内层。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;5-策略与层次&#34;&gt;5. 策略与层次&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;软件系统就是一种对&amp;quot;策略（policy）&amp;ldquo;的陈述：详细描述如何根据输入计算输出。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;架构要做的就是把策略按&lt;strong&gt;层次&lt;/strong&gt;分开：越靠近输入输出的策略层次越低（格式化、校验、IO），越远离的策略层次越高（业务规则）。高层策略与低层细节分离，低层策略应该成为高层策略的&amp;quot;插件&amp;rdquo;。&lt;/p&gt;
&lt;h3 id=&#34;6-业务逻辑实体与用例&#34;&gt;6. 业务逻辑：实体与用例&lt;/h3&gt;
&lt;p&gt;书里把业务逻辑分成两类：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;业务实体（Entity / 关键业务逻辑）&lt;/strong&gt;：与具体应用无关的、企业级最核心的规则。比如&amp;quot;贷款利率 = 基准利率 + 风险系数&amp;quot;，不管系统做成 Web、APP 还是命令行，这条规则都不变。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;用例（Use Case / 应用相关逻辑）&lt;/strong&gt;：描述某个具体应用如何使用实体。比如&amp;quot;贷款审批流程：先查征信 → 再评估额度 → 最后出审批结果&amp;quot;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;用例依赖实体，实体不知道用例的存在。&lt;strong&gt;实体是最通用、最高层、最不容易变的；用例次之。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&#34;7-尖叫的软件架构&#34;&gt;7. 尖叫的软件架构&lt;/h3&gt;
&lt;p&gt;这一章名字很有趣：&lt;strong&gt;你的架构应该&amp;quot;尖叫&amp;quot;着说出自己是做什么的&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;看一个系统的目录结构，如果看到的是 &lt;code&gt;models / views / controllers&lt;/code&gt;，这是框架在尖叫；如果看到的是 &lt;code&gt;order / payment / shipping&lt;/code&gt;（订单、支付、物流），这是业务在尖叫。好的架构应该一眼看出这是一个&amp;quot;图书管理系统&amp;quot;或&amp;quot;电商系统&amp;quot;，而不是&amp;quot;一个 Spring MVC 项目&amp;quot;。框架是工具，不应该喧宾夺主。&lt;/p&gt;
&lt;h3 id=&#34;8-整洁架构四层同心圆全书最著名的一张图&#34;&gt;8. 整洁架构：四层同心圆（全书最著名的一张图）&lt;/h3&gt;
&lt;p&gt;Bob 大叔综合了三种前辈架构思想——六边形架构（Hexagonal / Ports and Adapters）、DCI、BCE——提炼出统一的模型：&lt;strong&gt;整洁架构（The Clean Architecture）&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;这些架构的共同特点是系统：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;独立于框架&lt;/strong&gt;：框架是工具，不是牢笼；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可被测试&lt;/strong&gt;：业务逻辑可以脱离 UI、数据库、Web 服务测试；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;独立于 UI&lt;/strong&gt;：Web 界面换成命令行界面，业务逻辑一行不用改；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;独立于数据库&lt;/strong&gt;：Oracle 换成 MongoDB，业务逻辑不知道；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;独立于任何外部机构&lt;/strong&gt;：业务逻辑对外部接口一无所知。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;用 Mermaid 重绘经典的同心圆图（越靠内层，策略越高层、越抽象）：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    subgraph L4[&amp;#34;🟠 框架与驱动层 Frameworks &amp;amp; Drivers（最外层：实现细节）&amp;#34;]
        direction LR
        FW[Web 框架]
        DB[(数据库)]
        DEV[设备 / 外部接口]
    end
    subgraph L3[&amp;#34;🟡 接口适配层 Interface Adapters&amp;#34;]
        direction LR
        CTRL[Controllers]
        PRE[Presenters]
        GATE[Gateways]
    end
    subgraph L2[&amp;#34;🟢 用例层 Use Cases（应用业务逻辑）&amp;#34;]
        UC[用例交互器&amp;lt;br/&amp;gt;Use Case Interactor]
    end
    subgraph L1[&amp;#34;🔵 业务实体层 Entities（企业核心业务逻辑，最内层）&amp;#34;]
        ENT[业务实体]
    end
    L4 --&amp;gt;|依赖指向内层| L3
    L3 --&amp;gt;|依赖指向内层| L2
    L2 --&amp;gt;|依赖指向内层| L1&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;贯穿整个架构的只有一条&lt;strong&gt;依赖关系规则（The Dependency Rule）&lt;/strong&gt;：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;源码中的依赖关系必须只指向同心圆的内层，即由低层机制指向高层策略。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;内层代码不能引用外层代码中声明的任何名字——函数、类、变量，一概不行。外层使用的数据格式（尤其是框架生成的）也不能被内层使用。&lt;/p&gt;
&lt;p&gt;各层职责：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;业务实体（Entities）&lt;/strong&gt;：封装全系统最关键、最通用的业务逻辑，是整个系统中最不容易受外界变化影响的部分。页面导航改版、安全策略调整，都不应该触及实体层。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;用例（Use Cases）&lt;/strong&gt;：封装特定应用的业务逻辑，指挥数据在实体间流动，调度实体完成用例目标。用例的变化不影响实体，数据库/UI 的变化也不影响用例。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;接口适配器（Interface Adapters）&lt;/strong&gt;：一组数据格式转换器。GUI 的整个 MVC 都在这一层：Controllers、Presenters、Views。所有 SQL 语句也应该被限制在这一层。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;框架与驱动（Frameworks &amp;amp; Drivers）&lt;/strong&gt;：最外层，只写与内层沟通的胶水代码。Web 是细节，数据库是细节，都放在外层，&amp;ldquo;让它们伤不到内层&amp;rdquo;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：四层不是硬性规定，真正的系统可以多于四层，但依赖关系规则永远成立。&lt;/p&gt;
&lt;h3 id=&#34;9-一个完整的请求流程web--数据库的典型场景&#34;&gt;9. 一个完整的请求流程（Web + 数据库的典型场景）&lt;/h3&gt;
&lt;p&gt;书中最实用的一张图描述了一个请求如何穿越各层。用&amp;quot;用户下单&amp;quot;为例：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;sequenceDiagram
    participant U as 用户
    participant C as Controller&amp;lt;br/&amp;gt;(接口适配层)
    participant IB as InputBoundary&amp;lt;br/&amp;gt;(输入端口接口, 用例层)
    participant UCI as UseCaseInteractor&amp;lt;br/&amp;gt;(用例层)
    participant E as Entities&amp;lt;br/&amp;gt;(实体层)
    participant DAI as DataAccessInterface&amp;lt;br/&amp;gt;(数据访问接口, 用例层)
    participant DB as 数据库实现&amp;lt;br/&amp;gt;(最外层)
    participant OB as OutputBoundary&amp;lt;br/&amp;gt;(输出端口接口, 用例层)
    participant P as Presenter&amp;lt;br/&amp;gt;(接口适配层)
    participant V as View&amp;lt;br/&amp;gt;(最外层)

    U-&amp;gt;&amp;gt;C: 提交订单(HTTP 请求)
    C-&amp;gt;&amp;gt;IB: 包装成简单数据对象, 跨边界传入
    IB-&amp;gt;&amp;gt;UCI: 调用
    UCI-&amp;gt;&amp;gt;E: 调度实体执行业务规则
    UCI-&amp;gt;&amp;gt;DAI: 通过接口请求数据
    DAI-&amp;gt;&amp;gt;DB: 外层实现执行 SQL
    DB--&amp;gt;&amp;gt;UCI: 返回简单数据结构
    UCI-&amp;gt;&amp;gt;E: 收集结果
    UCI-&amp;gt;&amp;gt;OB: 组装 OutputData, 通过输出端口传出
    OB-&amp;gt;&amp;gt;P: Presenter 实现该接口
    P-&amp;gt;&amp;gt;V: 格式化成 ViewModel(纯字符串/开关)
    V--&amp;gt;&amp;gt;U: 渲染 HTML&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;三个要点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;控制流&lt;/strong&gt;从 Controller 出发，穿过用例，最终到达 Presenter——方向是从外到内再到外；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;源码依赖&lt;/strong&gt;每一步都指向内层：Controller 依赖 InputBoundary 接口、Presenter 实现 OutputBoundary 接口，两者都属于&amp;quot;外层依赖内层&amp;quot;；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;跨边界的数据&lt;/strong&gt;永远是&lt;strong&gt;简单的数据结构&lt;/strong&gt;（DTO / 函数参数 / POJO），绝不直接传递业务实体对象或数据库行结构——那会偷偷引入指向外层的依赖。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Presenter 的工作也很值得注意：它把 &lt;code&gt;OutputData&lt;/code&gt; 中的 &lt;code&gt;Date&lt;/code&gt;、&lt;code&gt;Currency&lt;/code&gt; 对象&lt;strong&gt;预先格式化&lt;/strong&gt;成字符串，把按钮名称、是否置灰的开关都放进 &lt;code&gt;ViewModel&lt;/code&gt;。于是 View 只剩下一件机械的事：把 ViewModel 搬进 HTML。&lt;/p&gt;
&lt;h3 id=&#34;10-展示器与谦卑对象&#34;&gt;10. 展示器与谦卑对象&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;谦卑对象（Humble Object）模式&lt;/strong&gt;是贯穿架构边界的通用模式：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;难以测试的代码（GUI、数据库网关、外部服务客户端）应该被压缩成尽量薄的&amp;quot;谦卑对象&amp;quot;，把可测试的逻辑抽到旁边的对象里。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;GUI：View 是谦卑对象（只搬数据，不测试）；Presenter 是可测试对象（负责格式化、决定按钮置灰）。&lt;/li&gt;
&lt;li&gt;数据库网关：SQL/ORM 部分是谦卑对象；用例与网关之间的接口实现是逻辑所在。&lt;/li&gt;
&lt;li&gt;服务网关：类似处理。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;11-不完全边界架构的成本控制&#34;&gt;11. 不完全边界：架构的成本控制&lt;/h3&gt;
&lt;p&gt;完整边界（双向接口 + 依赖反转）的构建和维护成本不低。敏捷社区会拿 YAGNI（&amp;ldquo;你不会需要它&amp;rdquo;）来反对预防性设计，但架构师的本职就是做预见。折中方案是&lt;strong&gt;不完全边界（Partial Boundary）&lt;/strong&gt;，书给了三种：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TD
    subgraph 方案一[&amp;#34;方案一：省掉最后一步&amp;#34;]
        C1[Client] --&amp;gt; SB1[组件A 与组件B&amp;lt;br/&amp;gt;已按完整边界设计好接口与数据格式&amp;lt;br/&amp;gt;但合并为单个组件编译部署]
    end
    subgraph 方案二[&amp;#34;方案二：单向边界（策略模式）&amp;#34;]
        C2[Client] --&amp;gt; IF[ServiceBoundary 接口]
        IMPL2[ServiceImpl] -.实现.-&amp;gt; IF
    end
    subgraph 方案三[&amp;#34;方案三：门户模式 Facade（最简）&amp;#34;]
        C3[Client] --&amp;gt; F[Facade 类&amp;lt;br/&amp;gt;只做调用转发]
        F --&amp;gt; S1[服务函数1]
        F --&amp;gt; S2[服务函数2]
    end&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;三种方式成本递减、保护强度也递减。实践启示：&lt;strong&gt;哪怕预算有限，也要至少用单向接口把边界的位置&amp;quot;占住&amp;quot;&lt;/strong&gt;，给将来升级成完整边界留好路。&lt;/p&gt;
&lt;h3 id=&#34;12-main-组件系统的脏秘密&#34;&gt;12. Main 组件：系统的&amp;quot;脏秘密&amp;quot;&lt;/h3&gt;
&lt;p&gt;每个系统都有一个&lt;strong&gt;最底层的细节：入口 Main 组件&lt;/strong&gt;。它是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;初始细节的所在地：负责创建所有工厂、策略、全局设施；&lt;/li&gt;
&lt;li&gt;最大的&amp;quot;具体&amp;quot;组件：它知道所有依赖关系，负责用依赖注入把它们组装起来；&lt;/li&gt;
&lt;li&gt;系统中&lt;strong&gt;唯一允许&amp;quot;知道一切&amp;quot;的组件&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;可以把它理解为插件系统的总装车间：所有插件都插到 Main 上，而插件们彼此互不相识。Spring 的 ApplicationContext 初始化代码，本质上就是 Main 组件。&lt;/p&gt;
&lt;h3 id=&#34;13-服务与测试&#34;&gt;13. 服务与测试&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;服务化（SOA/微服务）的收益被高估了&lt;/strong&gt;：服务之间的解耦往往不彻底（共享数据、共享网络），服务边界并不自动等于架构边界。架构边界由&amp;quot;依赖方向&amp;quot;决定，而不是由&amp;quot;是否拆成服务&amp;quot;决定。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;测试也是系统的一部分&lt;/strong&gt;：测试代码应该是系统中最独立的组件——通过测试 API 调用业务逻辑，而不是通过 UI 或真实数据库。测试套件本身也要遵守整洁架构，否则会脆弱到&amp;quot;一改代码就挂一片&amp;quot;，反过来吓得团队不敢重构。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;第六部分-实现细节震碎三观的四章&#34;&gt;第六部分 实现细节：震碎三观的四章&lt;/h2&gt;
&lt;p&gt;这一部分只反复强调一件事：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;数据库是细节。Web 是细节。框架是细节。它们都是可以被替换、被推迟决策的 IO 设备。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;数据库只是实现细节&#34;&gt;数据库只是实现细节&lt;/h3&gt;
&lt;p&gt;关系数据库本质上只是一种&amp;quot;把数据组织在磁盘上方便存取&amp;quot;的技术，它对业务逻辑而言就是一个细节。业务模型关注的是数据结构和操作，不是表结构和 SQL。今天用 MySQL，明天换 MongoDB，核心业务逻辑应该无感。&lt;/p&gt;
&lt;h3 id=&#34;web-是实现细节&#34;&gt;Web 是实现细节&lt;/h3&gt;
&lt;p&gt;Web 只是一种 IO 设备。过去几十年 GUI 技术换了一茬又一茬（桌面 → Web → 移动 → …），Web 也终将过时。架构要做的是把系统核心设计成与设备无关，让&amp;quot;通过 Web 交付&amp;quot;成为一种可插拔的交付方式。&lt;/p&gt;
&lt;h3 id=&#34;应用程序框架是实现细节&#34;&gt;应用程序框架是实现细节&lt;/h3&gt;
&lt;p&gt;框架作者对你的人生没有承诺——框架升级时的破坏式变更、框架的局限性绑架，都是真实风险。正确姿势：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;与框架保持&amp;quot;单向婚姻&amp;quot;：你可以依赖框架，但不让框架侵入核心业务代码。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;不要从框架基类派生业务对象，不要在核心业务层用框架的注解——把框架摁在最外层。&lt;/p&gt;
&lt;h3 id=&#34;案例分析视频销售网站&#34;&gt;案例分析：视频销售网站&lt;/h3&gt;
&lt;p&gt;书里用一个&amp;quot;在线卖视频的网站&amp;quot;贯穿分析了完整的架构过程：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;先识别&lt;strong&gt;用例和 actor&lt;/strong&gt;（购买者、管理员、作者等）；&lt;/li&gt;
&lt;li&gt;按 actor 和变更原因划分&lt;strong&gt;组件边界&lt;/strong&gt;（视图、用例、实体、数据库各自成组）；&lt;/li&gt;
&lt;li&gt;用&lt;strong&gt;依赖反转&lt;/strong&gt;保证所有依赖指向高层策略；&lt;/li&gt;
&lt;li&gt;最后得到一个可以按团队分工并行开发、按需单体或分组件部署的结构。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;结论：架构设计的输入不是技术选型，而是&lt;strong&gt;用例、行为者、变更原因&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id=&#34;拾遗simon-brown-执笔打包方式的争论&#34;&gt;拾遗（Simon Brown 执笔）：打包方式的争论&lt;/h3&gt;
&lt;p&gt;最后一章讨论了四种代码组织方式，相当接地气：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TD
    A[&amp;#34;① 按层打包&amp;lt;br/&amp;gt;controllers/ services/ repositories/&amp;lt;br/&amp;gt;（经典但业务意图不明显）&amp;#34;] 
    B[&amp;#34;② 按功能打包&amp;lt;br/&amp;gt;orders/ customers/ payments/&amp;lt;br/&amp;gt;（业务意图清晰）&amp;#34;]
    C[&amp;#34;③ 端口与适配器&amp;lt;br/&amp;gt;domain 与 infrastructure 分离&amp;lt;br/&amp;gt;（六边形架构风格）&amp;#34;]
    D[&amp;#34;④ 按组件打包&amp;lt;br/&amp;gt;粗粒度组件 + 严格的访问控制&amp;lt;br/&amp;gt;（Bob 大叔推荐，配合编译器/访问修饰符强制隔离）&amp;#34;]
    A -.演进.-&amp;gt; B -.演进.-&amp;gt; C -.演进.-&amp;gt; D&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;要点是：无论选哪种，&lt;strong&gt;关键是用编译器（访问修饰符、模块系统）来强制执行依赖规则&lt;/strong&gt;，而不是靠&amp;quot;大家自觉&amp;quot;。架构约定如果没有工具强制，迟早被腐蚀。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;实战举例一个订单服务的整洁化改造&#34;&gt;实战举例：一个订单服务的&amp;quot;整洁化&amp;quot;改造&lt;/h2&gt;
&lt;p&gt;光看理论容易晕，用一个简化的 Java/Spring 风格例子收尾。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;改造前&lt;/strong&gt;（典型的三层贫血架构，业务逻辑泄漏到各处）：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Controller 直接调 Repository，业务规则散落在 SQL 和 Service 里&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@RestController&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;OrderController&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;@Autowired&lt;/span&gt; OrderRepository repo;      &lt;span style=&#34;color:#75715e&#34;&gt;// 直接依赖 JPA/SQL&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;@PostMapping&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/orders&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;create&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;@RequestBody&lt;/span&gt; OrderEntity e) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (e.&lt;span style=&#34;color:#a6e22e&#34;&gt;getAmount&lt;/span&gt;() &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; 10000 &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;e.&lt;span style=&#34;color:#a6e22e&#34;&gt;getVip&lt;/span&gt;()) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; RuntimeException(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;超限&amp;#34;&lt;/span&gt;);   &lt;span style=&#34;color:#75715e&#34;&gt;// 业务规则混在 Web 层&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        repo.&lt;span style=&#34;color:#a6e22e&#34;&gt;save&lt;/span&gt;(e);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;问题：想换成命令行下单？重写。想换掉数据库？重写。想单测这条&amp;quot;超 1 万必须是 VIP&amp;quot;的规则？得把 Spring 容器和数据库拉起来。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;改造后&lt;/strong&gt;（按整洁架构分层）：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// ① 实体层：与企业无关环境的核心规则，零依赖&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;OrderPolicy&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;boolean&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;canPlace&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;long&lt;/span&gt; amount, &lt;span style=&#34;color:#66d9ef&#34;&gt;boolean&lt;/span&gt; vip) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; amount &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;=&lt;/span&gt; 10000 &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt; vip;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// ② 用例层：定义输入/输出端口，依赖反转&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;interface&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;OrderGateway&lt;/span&gt; {                 &lt;span style=&#34;color:#75715e&#34;&gt;// 用例层定义的接口（输出端口）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;save&lt;/span&gt;(String orderId, &lt;span style=&#34;color:#66d9ef&#34;&gt;long&lt;/span&gt; amount, &lt;span style=&#34;color:#66d9ef&#34;&gt;boolean&lt;/span&gt; vip);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;PlaceOrderUseCase&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;final&lt;/span&gt; OrderGateway gateway;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    PlaceOrderUseCase(OrderGateway gateway) { &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;gateway&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; gateway; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    String &lt;span style=&#34;color:#a6e22e&#34;&gt;execute&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;long&lt;/span&gt; amount, &lt;span style=&#34;color:#66d9ef&#34;&gt;boolean&lt;/span&gt; vip) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;OrderPolicy.&lt;span style=&#34;color:#a6e22e&#34;&gt;canPlace&lt;/span&gt;(amount, vip))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;REJECTED&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        String id &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; UUID.&lt;span style=&#34;color:#a6e22e&#34;&gt;randomUUID&lt;/span&gt;().&lt;span style=&#34;color:#a6e22e&#34;&gt;toString&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        gateway.&lt;span style=&#34;color:#a6e22e&#34;&gt;save&lt;/span&gt;(id, amount, vip);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; id;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// ③ 接口适配层 + 最外层：Controller / Repository 实现端口&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@RestController&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;OrderController&lt;/span&gt; {                    &lt;span style=&#34;color:#75715e&#34;&gt;// Controller 是适配器&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;final&lt;/span&gt; PlaceOrderUseCase useCase;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    OrderController(PlaceOrderUseCase u) { &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;useCase&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; u; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;@PostMapping&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/orders&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ResponseEntity&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;String&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;create&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;@RequestBody&lt;/span&gt; OrderRequest req) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; ResponseEntity.&lt;span style=&#34;color:#a6e22e&#34;&gt;ok&lt;/span&gt;(useCase.&lt;span style=&#34;color:#a6e22e&#34;&gt;execute&lt;/span&gt;(req.&lt;span style=&#34;color:#a6e22e&#34;&gt;amount&lt;/span&gt;(), req.&lt;span style=&#34;color:#a6e22e&#34;&gt;vip&lt;/span&gt;()));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@Repository&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;SqlOrderGateway&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;implements&lt;/span&gt; OrderGateway {  &lt;span style=&#34;color:#75715e&#34;&gt;// 数据库是插件&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 这里可以写 SQL/JPA，内层完全不知道&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;save&lt;/span&gt;(String id, &lt;span style=&#34;color:#66d9ef&#34;&gt;long&lt;/span&gt; amount, &lt;span style=&#34;color:#66d9ef&#34;&gt;boolean&lt;/span&gt; vip) { &lt;span style=&#34;color:#75715e&#34;&gt;/* INSERT ... */&lt;/span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;改造带来的可验证收益：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;可测试&lt;/strong&gt;：&lt;code&gt;new PlaceOrderUseCase(inMemoryGateway)&lt;/code&gt; 直接跑单元测试，不需要 Spring、不需要数据库；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可替换 UI&lt;/strong&gt;：再加一个 &lt;code&gt;OrderCliRunner&lt;/code&gt; 调用同一个 &lt;code&gt;PlaceOrderUseCase&lt;/code&gt;，一行用例代码不改；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可替换数据库&lt;/strong&gt;：写一个 &lt;code&gt;MongoOrderGateway implements OrderGateway&lt;/code&gt;，在 Main（配置类）里换掉注入的实现即可。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这个例子里每一条收益，都直接来自&amp;quot;依赖关系规则&amp;quot;那一条铁律。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;全书要点一页纸&#34;&gt;全书要点一页纸&lt;/h2&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;部分&lt;/th&gt;
					&lt;th&gt;核心问题&lt;/th&gt;
					&lt;th&gt;一句话答案&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;概述&lt;/td&gt;
					&lt;td&gt;架构是为了什么&lt;/td&gt;
					&lt;td&gt;最小化系统全生命周期的人力成本&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;编程范式&lt;/td&gt;
					&lt;td&gt;代码的底层约束&lt;/td&gt;
					&lt;td&gt;三大范式都是&amp;quot;剥夺能力&amp;quot;：限制 goto、函数指针、赋值&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;SOLID&lt;/td&gt;
					&lt;td&gt;类与函数怎么设计&lt;/td&gt;
					&lt;td&gt;中层五原则，其中 DIP 是架构边界的武器&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;组件原则&lt;/td&gt;
					&lt;td&gt;类怎么打包、组件怎么依赖&lt;/td&gt;
					&lt;td&gt;聚合（REP/CCP/CRP）与耦合（ADP/SDP/SAP）的权衡&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;软件架构&lt;/td&gt;
					&lt;td&gt;系统怎么分层&lt;/td&gt;
					&lt;td&gt;划边界 + 依赖规则：依赖永远指向高层策略&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;实现细节&lt;/td&gt;
					&lt;td&gt;数据库/Web/框架是什么&lt;/td&gt;
					&lt;td&gt;都是细节、都是插件、都可以推迟决策&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;如果只能记住一句话：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;好的架构让最重要的东西（业务规则）不依赖任何东西，让最不重要的东西（数据库、Web、框架）依赖业务规则——依赖方向永远由外向内。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;参考资料：《Clean Architecture: A Craftsman&amp;rsquo;s Guide to Software Structure and Design》，Robert C. Martin 著，&lt;a href=&#34;https://github.com/gdut-yy/Clean-Architecture-zh&#34;&gt;中文译本&lt;/a&gt;。&lt;/em&gt;&lt;/p&gt;
</description>
    </item>
    <item>
      <title>《持续交付》笔记：每步可退，把集成风险日常化</title>
      <link>https://ftmao.com/blog/bookstore/continuous-delivery-book-notes/</link>
      <pubDate>Fri, 08 Sep 2023 10:40:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/bookstore/continuous-delivery-book-notes/</guid>
      <description>&lt;h1 id=&#34;持续交付笔记每步可退把集成风险日常化&#34;&gt;《持续交付》笔记：每步可退，把集成风险日常化&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;《持续交付》（Continuous Delivery: Reliable Software Releases through Build, Test, and Deployment Automation）是 Jez Humble 和 David Farley 2010 年的作品，拿过 2011 年的 Jolt 大奖。这本书可以说是 DevOps 运动的奠基文献之一——后来《DevOps Handbook》《Accelerate》这些书，根子上都能追溯到这里。&lt;/p&gt;
&lt;p&gt;全书 500 多页，核心其实就一个问题：&lt;strong&gt;怎么让&amp;quot;把软件放到生产环境&amp;quot;这件事，从一年几次的高风险仪式，变成随时可以按一下按钮的平常操作？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我读完最大的感受是：这本书表面讲工具和流程，骨子里讲的是一种风险管理哲学——&lt;strong&gt;不消除风险，而是把风险切碎、摊平到每一天&lt;/strong&gt;。这篇笔记是我消化后的版本，不是章节摘要。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;一全书的核心主张一张图看懂&#34;&gt;一、全书的核心主张：一张图看懂&lt;/h2&gt;
&lt;p&gt;先把整本书的骨架画出来，后面每节都能对号入座：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TB
    subgraph P[&amp;#34;核心哲学：把风险日常化&amp;#34;]
        PHIL[&amp;#34;&amp;#39;If it hurts,&amp;lt;br/&amp;gt;do it more often&amp;#39;&amp;#34;]
    end
    subgraph PL[&amp;#34;部署流水线 Deployment Pipeline&amp;#34;]
        S1[&amp;#34;① 提交阶段&amp;lt;br/&amp;gt;编译 + 单元测试&amp;lt;br/&amp;gt;分钟级反馈&amp;#34;]
        S2[&amp;#34;② 自动化验收&amp;lt;br/&amp;gt;验收测试 + 容量/安全测试&amp;lt;br/&amp;gt;小时级反馈&amp;#34;]
        S3[&amp;#34;③ 手工验证&amp;lt;br/&amp;gt;UAT / 探索性测试 / 演示&amp;#34;]
        S4[&amp;#34;④ 发布&amp;lt;br/&amp;gt;点按钮，随时可发&amp;#34;]
        S1 --&amp;gt; S2 --&amp;gt; S3 --&amp;gt; S4
    end
    subgraph F[&amp;#34;支撑地基&amp;#34;]
        F1[&amp;#34;配置管理&amp;lt;br/&amp;gt;一切皆版本控制&amp;#34;]
        F2[&amp;#34;自动化测试金字塔&amp;#34;]
        F3[&amp;#34;主干开发 + 特性开关&amp;#34;]
    end
    PHIL --&amp;gt; PL
    F --&amp;gt; PL&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;这本书的立论链条是这样的：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;软件发布之所以痛苦，是因为它&lt;strong&gt;罕见&lt;/strong&gt;——几个月才发一次，每次集成了几百个变更，出问题没法定位；&lt;/li&gt;
&lt;li&gt;解决思路不是&amp;quot;更小心地发布&amp;quot;，而是&lt;strong&gt;更频繁地发布&lt;/strong&gt;——频率高了，每次变更就小，小了就好定位、好回滚；&lt;/li&gt;
&lt;li&gt;要支撑高频发布，必须有一条自动化的&lt;strong&gt;部署流水线&lt;/strong&gt;，让每个提交自动走完所有质量关卡；&lt;/li&gt;
&lt;li&gt;流水线要可靠，底下得有配置管理、自动化测试、增量式开发三块地基。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;感悟&lt;/strong&gt;：这个逻辑其实和健身一个道理——很久不运动，偶尔跑一次十公里肯定受伤；每天跑两公里反而没事。发布也一样，&amp;ldquo;少做&amp;quot;不是降低风险的办法，&amp;ldquo;把每次做小&amp;quot;才是。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;二if-it-hurts-do-it-more-often全书最反直觉的一句话&#34;&gt;二、&amp;ldquo;If it hurts, do it more often&amp;rdquo;：全书最反直觉的一句话&lt;/h2&gt;
&lt;p&gt;书里有一句被广泛引用的话：&lt;strong&gt;&amp;ldquo;如果某件事让你痛苦，那就更频繁地去做它&amp;rdquo;&lt;/strong&gt;（If it hurts, do it more often, and bring the pain forward）。&lt;/p&gt;
&lt;p&gt;直觉上，痛苦的事应该少做。但作者的论证是：痛苦的根源是&lt;strong&gt;批量太大、间隔太久&lt;/strong&gt;。以集成为例：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;两个程序员各写两周代码再合并，冲突一大堆，合并花两天——于是大家说&amp;quot;少合并&amp;rdquo;；&lt;/li&gt;
&lt;li&gt;正确的解法恰恰相反：&lt;strong&gt;每天合并多次&lt;/strong&gt;，每次冲突只有几行，顺手就解了。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;同样的道理适用于：数据库迁移、发布上线、甚至和客户的沟通。频率提高会逼着你把流程自动化、把每一步做小，而&amp;quot;小&amp;quot;本身就是最好的风险对冲。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;踩坑记录（书外）&lt;/strong&gt;：我待过的一个团队，发布周期是一个月一次，每次发布日都要通宵，发布完还要留人值班一周&amp;quot;灭火&amp;rdquo;。所有人的共识是&amp;quot;发布太危险，得再慎重些&amp;quot;——于是变更冻结期越来越长，每次积压的变更越来越多，发布越来越危险。这是典型的&lt;strong&gt;用制造问题的方式解决问题&lt;/strong&gt;。后来换了 CTO 强推两周一次、再到一周一次，逼着团队把部署脚本自动化、把回滚预案做出来，半年后发布变成了一个普通下午就能做的事。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;感悟&lt;/strong&gt;：&amp;ldquo;痛苦&amp;quot;是个信号，但它指示的方向经常和直觉相反。看到团队里某件事人人回避，正确的反应不是绕着走，而是问：能不能把它自动化、高频化、常态化？&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;三部署流水线每个提交都是可发布候选&#34;&gt;三、部署流水线：每个提交都是可发布候选&lt;/h2&gt;
&lt;p&gt;这是全书最核心的工程概念。部署流水线（deployment pipeline）的正式定义是：&lt;strong&gt;从代码提交到生产发布之间，所有验证步骤的自动化呈现&lt;/strong&gt;。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    C[&amp;#34;提交 Commit&amp;lt;br/&amp;gt;触发流水线&amp;#34;] --&amp;gt; B[&amp;#34;① 构建 + 单元测试&amp;lt;br/&amp;gt;编译 / 静态检查&amp;lt;br/&amp;gt;几分钟内出结果&amp;#34;]
    B --&amp;gt; A[&amp;#34;② 自动化验收测试&amp;lt;br/&amp;gt;面向业务的端到端场景&amp;lt;br/&amp;gt;在类生产环境跑&amp;#34;]
    A --&amp;gt; U[&amp;#34;③ 手工验证&amp;lt;br/&amp;gt;UAT / 探索性测试&amp;lt;br/&amp;gt;非功能需求确认&amp;#34;]
    U --&amp;gt; R[&amp;#34;④ 发布&amp;lt;br/&amp;gt;同一构建产物&amp;lt;br/&amp;gt;一键部署到生产&amp;#34;]
    B -.-&amp;gt;|&amp;#34;失败即止&amp;#34;| X[&amp;#34;❌ 流水线终止&amp;lt;br/&amp;gt;修复优先于新功能&amp;#34;]
    A -.-&amp;gt;|&amp;#34;失败即止&amp;#34;| X&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;几个关键点，都是容易理解偏的：&lt;/p&gt;
&lt;h3 id=&#34;31-每个阶段都是质量关卡失败即止&#34;&gt;3.1 每个阶段都是质量关卡，失败即止&lt;/h3&gt;
&lt;p&gt;流水线的本质是&lt;strong&gt;递进式的信心建设&lt;/strong&gt;：便宜的、快的测试放前面（单元测试几秒出结果），贵的、慢的放后面（验收测试、性能测试）。任何一个阶段失败，这个构建版本就&amp;quot;死&amp;quot;了，不进入下一阶段。&lt;/p&gt;
&lt;p&gt;这带来一条重要的团队纪律：&lt;strong&gt;流水线红了，修复它就是最高优先级&lt;/strong&gt;——比写新功能优先。因为流水线红着的时候，后面所有人的提交都拿不到反馈，等于团队集体失明。书里管这个叫&amp;quot;不要带着broken的构建回家&amp;rdquo;。&lt;/p&gt;
&lt;h3 id=&#34;32-每次提交都产生一个可发布候选&#34;&gt;3.2 每次提交都产生一个可发布候选&lt;/h3&gt;
&lt;p&gt;这句话值得单独强调：&lt;strong&gt;流水线的每个成功产出都是 release candidate&lt;/strong&gt;。注意是&amp;quot;候选&amp;quot;不是&amp;quot;一定发&amp;quot;——它保证了技术上随时可以发，发不发是业务决策（这点在第六节展开）。&lt;/p&gt;
&lt;p&gt;做到这一点有个硬要求：&lt;strong&gt;部署到每个环境的必须是同一个构建产物&lt;/strong&gt;。测试环境用 A 包、生产环境重新打一个 B 包，等于你在生产环境跑了一个从没被验证过的东西——测试全白做了。正确的做法是：构建只发生一次，产出的二进制包（jar、镜像）在流水线里逐级晋升（promote），环境差异全部由外部配置注入。&lt;/p&gt;
&lt;h3 id=&#34;33-流水线的反馈速度是硬指标&#34;&gt;3.3 流水线的反馈速度是硬指标&lt;/h3&gt;
&lt;p&gt;提交阶段要在几分钟内给出结果——慢了就没人愿意等，大家开始绕过流程。验收测试可以慢一些，但也要控制在小时级。如果一个阶段慢到天天被跳过，那它不是&amp;quot;严格&amp;quot;，是&amp;quot;无效&amp;quot;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;感悟&lt;/strong&gt;：流水线画出来都不难，难的是维护它的&amp;quot;纪律成本&amp;quot;。红了几天的流水线比没有流水线更糟——它教会团队的是&amp;quot;告警可以无视&amp;quot;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;四自动化测试金字塔而不是冰淇淋筒&#34;&gt;四、自动化测试：金字塔而不是冰淇淋筒&lt;/h2&gt;
&lt;p&gt;书里的测试分层思想，后来被大家画成了著名的&amp;quot;测试金字塔&amp;quot;（Mike Cohn 提出，本书把它发扬光大了）：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TB
    subgraph 金字塔[&amp;#34;自动化测试金字塔（数量：下多上少；成本：下低上高）&amp;#34;]
        E2E[&amp;#34;少量：端到端 UI 测试&amp;lt;br/&amp;gt;慢、脆、维护贵&amp;#34;]
        ACC[&amp;#34;适量：验收/服务层测试&amp;lt;br/&amp;gt;面向业务行为&amp;lt;br/&amp;gt;不依赖 UI&amp;#34;]
        UNIT[&amp;#34;大量：单元测试&amp;lt;br/&amp;gt;毫秒级、定位精准&amp;#34;]
    end
    UNIT --&amp;gt; ACC --&amp;gt; E2E&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;书中几个至今仍然尖锐的观点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;验收测试要用业务语言写，由开发者、测试、业务三方共同拥有&lt;/strong&gt;。它不只是测试，更是&amp;quot;可执行的规格说明书&amp;quot;——需求变了，先改验收测试；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;别把测试挂在 UI 上&lt;/strong&gt;。UI 是系统里最不稳定的层，通过 UI 驱动的测试又慢又脆。验收测试应该打在 UI 之下的服务/API 层；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;手工测试不会被消灭，但角色变了&lt;/strong&gt;：从&amp;quot;重复执行回归用例&amp;quot;变成&amp;quot;探索性测试、可用性评估&amp;quot;——机器做重复的，人做需要判断力的。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;反面形态叫&amp;quot;冰淇淋筒&amp;quot;：大量手工测试 + 一堆脆弱的 UI 自动化 + 几乎没有单元测试。症状是测试跑一遍要一整天、失败了大半天定位不到问题、最后团队对测试结果失去信心开始无视红灯。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;感悟&lt;/strong&gt;：测试金字塔真正的价值不在&amp;quot;比例&amp;quot;，在它背后的经济学：&lt;strong&gt;反馈越快、定位越准的测试越便宜，应该最多&lt;/strong&gt;。所有&amp;quot;冰淇淋筒&amp;quot;团队的问题，本质都是把测试成本结构搞反了。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;五增量式发布把风险一刀一刀切小&#34;&gt;五、增量式发布：把风险一刀一刀切小&lt;/h2&gt;
&lt;p&gt;这部分是全书我最服气的一章——它回答了一个实际问题：大功能开发要好几个月，总不能这几个月不发版吧？书里给的答案是&lt;strong&gt;把&amp;quot;部署&amp;quot;和&amp;quot;发布&amp;quot;解耦&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id=&#34;51-主干开发--特性开关和长寿命分支说再见&#34;&gt;5.1 主干开发 + 特性开关：和长寿命分支说再见&lt;/h3&gt;
&lt;p&gt;业界常见的做法是&amp;quot;功能分支&amp;quot;：每个大功能拉一个分支，开发几个月再合并。书里的态度很明确：&lt;strong&gt;长寿命分支是持续交付的头号敌人&lt;/strong&gt;。分支活得越久，合并那天的集成爆炸越大——你只是把集成风险攒起来，并没有消除它。&lt;/p&gt;
&lt;p&gt;替代方案是&lt;strong&gt;主干开发（trunk-based development）&lt;/strong&gt;：所有人都频繁提交到主干（至少每天），没做完的大功能用**特性开关（feature toggle）**藏起来——代码已经在生产环境，但功能入口被开关挡住，用户看不见。&lt;/p&gt;
&lt;p&gt;这样做的好处是双重的：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;集成每天都在发生，冲突永远是小的；&lt;/li&gt;
&lt;li&gt;&amp;ldquo;部署新代码&amp;quot;和&amp;quot;向用户开放功能&amp;quot;变成了两件独立的事。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;代价也要认账：开关本身是技术债，上线后要记得清理；半成品代码要保证&amp;quot;编译得过、不破坏现有行为&amp;rdquo;，对代码组织（抽象分支、按业务边界拆分）有要求。&lt;/p&gt;
&lt;h3 id=&#34;52-蓝绿部署与金丝雀发布&#34;&gt;5.2 蓝绿部署与金丝雀发布&lt;/h3&gt;
&lt;p&gt;发布阶段的两个经典模式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;蓝绿部署（blue-green deployment）&lt;/strong&gt;：维护两套生产环境。当前流量在&amp;quot;蓝&amp;quot;，新版本部署到&amp;quot;绿&amp;quot;，验证没问题就把流量切过去。出问题？切回来就是秒级回滚。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;金丝雀发布（canary release）&lt;/strong&gt;：先把新版本暴露给一小部分流量（比如 1% 的用户或一台服务器），盯着错误率和关键指标，没问题再逐步扩大范围。名字来自煤矿里的金丝雀——鸟先下井，出事人有时间撤。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;两者的共同点：&lt;strong&gt;回滚能力内建在发布机制里&lt;/strong&gt;，而不是出事后临时想办法。书里有句话大意是：发布计划里最重要的一条是回滚计划，而回滚计划必须&lt;strong&gt;真的演练过&lt;/strong&gt;才算数——没演练过的回滚方案约等于没有。&lt;/p&gt;
&lt;h3 id=&#34;53-数据库变更expand-contract-模式&#34;&gt;5.3 数据库变更：expand-contract 模式&lt;/h3&gt;
&lt;p&gt;蓝绿部署最怕的拦路虎是数据库：两套环境共用一个库，schema 一变，新旧代码没法同时跑。书里的解法是&lt;strong&gt;expand-contract（扩张-收缩）模式&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Expand&lt;/strong&gt;：先做一次只做&amp;quot;加&amp;quot;的变更——加新列、加新表，旧的什么都不动。这次变更对旧代码完全兼容，可以安全部署；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;迁移&lt;/strong&gt;：应用代码改为同时读写新旧结构（或双写），后台任务把存量数据搬过去；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Contract&lt;/strong&gt;：等所有在用版本都不依赖旧结构了，再单独做一次变更删掉旧列、旧表。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;核心思想一句话：&lt;strong&gt;每一次变更都向前向后兼容，让 schema 演进和应用部署可以分开走&lt;/strong&gt;。这也解释了为什么&amp;quot;上线一个大变更顺便重构一下表结构&amp;quot;是事故高发操作——你把两个本可以解耦的风险捆在了一起。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;六配置与环境把环境也当代码管&#34;&gt;六、配置与环境：把环境也当代码管&lt;/h2&gt;
&lt;p&gt;这本书反复强调的一个纪律是：&lt;strong&gt;一切进版本控制&lt;/strong&gt;——不只是源代码，还包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;构建脚本、部署脚本；&lt;/li&gt;
&lt;li&gt;应用配置（环境相关的那部分参数化）；&lt;/li&gt;
&lt;li&gt;数据库迁移脚本；&lt;/li&gt;
&lt;li&gt;环境本身的定义（后来演化成了 Infrastructure as Code，IaC）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;理由很朴素：如果环境的搭建靠&amp;quot;运维同学的记忆和一台手工调出来的服务器&amp;quot;，那你的测试环境和生产环境一定存在没人知道的差异，而每个差异都是一颗雷。书里对这种手工维护的服务器有个后来的流行叫法——&lt;strong&gt;宠物（pets） vs 牲畜（cattle）&lt;/strong&gt;：宠物有名字，病了要治；牲畜只有编号，病了换掉重来。环境应该朝&amp;quot;牲畜&amp;quot;方向走：不可变、可重建、随时销毁再拉起。&lt;/p&gt;
&lt;p&gt;配置管理上的一条实用原则：&lt;strong&gt;把&amp;quot;什么变&amp;quot;和&amp;quot;什么不变&amp;quot;分开&lt;/strong&gt;。代码和各环境共用的配置进版本库，环境差异（连接串、密钥、开关值）由部署流程注入。同一份代码 + 不同的配置 = 不同的环境，而不是各环境维护不同的代码分支。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;七发布是业务决策不是技术决策&#34;&gt;七、发布是业务决策，不是技术决策&lt;/h2&gt;
&lt;p&gt;这是全书观点中我认为最容易被低估、也最改变组织行为的一条。&lt;/p&gt;
&lt;p&gt;传统模式下，&amp;ldquo;什么时候发布&amp;quot;是技术团队说了算的——因为只有他们觉得&amp;quot;差不多了&amp;quot;才敢发，发布本身是高危操作，自然由承担风险的人拍板。&lt;/p&gt;
&lt;p&gt;持续交付把这件事倒过来：当流水线保证了&lt;strong&gt;每个提交技术上都可发布&lt;/strong&gt;之后，&amp;ldquo;何时发布、对谁发布&amp;quot;就纯粹变成了业务问题——市场时机、合规窗口、用户沟通节奏，都应该由产品和业务来定。技术团队的职责是保证这个按钮&lt;strong&gt;随时可以按、按了不出事、出了事能秒回&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;这也是特性开关、金丝雀发布的深层价值：它们把&amp;quot;发布&amp;quot;从技术动作变成了业务杠杆——按客户群分批开放、给内部员工先试用、关键客户定制节奏，都是业务手段而不是工程手段。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;感悟&lt;/strong&gt;：很多团队的技术债，根子其实是&amp;quot;发布权力错配&amp;rdquo;：业务想要的速度给不了，因为发布在技术上做不到随时可做。先把工程能力补到位，权责自然归位。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;八案例正面与反面&#34;&gt;八、案例：正面与反面&lt;/h2&gt;
&lt;h3 id=&#34;正面高频部署的先行者&#34;&gt;正面：高频部署的先行者&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Flickr&lt;/strong&gt;：2009 年 Velocity 大会上，Flickr 的 John Allspaw 和 Paul Hammond 做了那场著名演讲《10+ Deploys Per Day: Dev and Ops Cooperation at Flickr》——每天 10 次以上部署。这个演讲被公认为 DevOps 运动的引爆点之一。他们的经验核心正是本书的思想：小批量、特性开关、完善的监控和一键回滚，让高频发布不是冒险而是常态。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Amazon&lt;/strong&gt;：以部署频率著称。后来 Amazon 高管在公开分享中提到过，2011 年前后 Amazon 平均每隔十几秒就有一处变更部署到生产（这个数字随年份增长，到 2014 年的分享里已经是秒级）。支撑它的是高度自动化的流水线和&amp;quot;谁构建谁运行&amp;quot;的服务负责制。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些公司证明了一件事：&lt;strong&gt;部署频率和生产稳定性不是此消彼长，而是可以同向增长的&lt;/strong&gt;——前提是自动化、小批量、快速回滚三件套齐全。后来 DORA 的年度行业报告用数据反复验证了这一点：部署频率最高的那批组织，变更失败率反而最低、恢复最快。&lt;/p&gt;
&lt;h3 id=&#34;反面knight-capital45-分钟-44-亿美元&#34;&gt;反面：Knight Capital，45 分钟 4.4 亿美元&lt;/h3&gt;
&lt;p&gt;2012 年 8 月 1 日，美国做市商 Knight Capital 发生了一起教科书级的部署事故，大约 45 分钟内亏损约 4.4 亿美元，公司险些倒闭，最终被收购。&lt;/p&gt;
&lt;p&gt;事后的公开报道和美国 SEC 的调查文件还原了链条：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;他们为新的&amp;quot;零售流动性计划&amp;rdquo;（Retail Liquidity Program）功能做部署，生产环境共 &lt;strong&gt;8 台服务器，人工部署只更新了 7 台&lt;/strong&gt;，漏了一台；&lt;/li&gt;
&lt;li&gt;新代码&lt;strong&gt;复用了一个旧的标志位（flag）&lt;/strong&gt;，而这个标志位多年前曾用于一个早已弃用的功能&amp;quot;Power Peg&amp;quot;——沉睡的旧代码没有被删除，只是被开关挡着；&lt;/li&gt;
&lt;li&gt;那台漏更新的服务器上，旧标志位被请求触发，&lt;strong&gt;沉睡多年的 Power Peg 代码被唤醒&lt;/strong&gt;，开始以错误逻辑疯狂下单；&lt;/li&gt;
&lt;li&gt;现场&lt;strong&gt;没有一键回滚机制&lt;/strong&gt;，工程师手忙脚乱，花了 40 多分钟才止住。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;用本书的概念框架看，这个事故几乎踩中了所有反面：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;部署靠人工&lt;/strong&gt;——8 台漏 1 台是人工部署的必然概率问题，不是偶发失误；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;死代码 + 标志位复用&lt;/strong&gt;——特性开关没被治理，旧开关复用、死代码不清理，开关从保护伞变成了定时炸弹；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;没有快速回滚&lt;/strong&gt;——如果有一键回滚，损失可能是零头。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;感悟&lt;/strong&gt;：这个案例我逢人就讲，因为它完美诠释了本书的中心论点：&lt;strong&gt;事故的根源不是&amp;quot;发布太多&amp;quot;，而是发布过程不可重复、不可回滚&lt;/strong&gt;。Knight Capital 一年也发不了几次版，&amp;ldquo;少发布&amp;quot;没有保护他们。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;九思考与感悟&#34;&gt;九、思考与感悟&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&amp;ldquo;持续&amp;quot;是方法，&amp;ldquo;交付信心&amp;quot;才是目的。&lt;/strong&gt; 读完回头看，这本书所有技术手段——流水线、金字塔、特性开关、蓝绿——都服务于同一件事：让团队在任何时候都能有底气地说&amp;quot;现在的版本是可发布的&amp;rdquo;。这种底气本身会改变团队的开发方式：敢重构、敢提交、敢试验。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;纪律比工具难买。&lt;/strong&gt; 流水线的搭建一个 Sprint 就能做完，难的是&amp;quot;红了就停下手头一切去修&amp;quot;这条纪律。工具解决的是&amp;quot;能不能&amp;rdquo;，纪律解决的是&amp;quot;做不做&amp;rdquo;。后者只能靠团队共识和管理者背书。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;和客户开发方法论同构。&lt;/strong&gt; 这是我读完联想最多的一点：之前读的创业方法论里，客户开发（Customer Development）讲的是市场侧的小步试错——别闭门造车一年再赌一把大的，把假设拆小，快速放到真实客户面前验证。持续交付是&lt;strong&gt;工程侧的同一件事&lt;/strong&gt;：别把代码攒三个月再赌一次大发布，把变更拆小，快速推到真实环境里验证。一个在验证&amp;quot;需求假设&amp;quot;，一个在验证&amp;quot;代码正确性假设&amp;quot;，底层的认识论完全一致：&lt;strong&gt;小批量、快反馈、可回退，用高频试错替代单次豪赌&lt;/strong&gt;。精益创业（Lean Startup）把这两条线拧在了一起，不是巧合——Eric Ries 本人就是持续部署的早期实践者。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;每步可退，才能每步敢进。&lt;/strong&gt; &amp;ldquo;可回滚&amp;quot;听起来是防御性能力，实际上是进攻性能力——只有确信每一步都退得回去，团队才敢把步子迈快。这本书最深的洞察也许就这一句：&lt;strong&gt;速度和安全不是天平的两端，可回滚性把它们焊在了同一侧。&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id=&#34;本篇小结&#34;&gt;本篇小结&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;部署流水线是核心骨架：提交 → 构建+单元测试 → 自动化验收 → 手工验证/UAT → 发布，每个阶段是质量关卡，失败即止，修复红灯优先于新功能；&lt;/li&gt;
&lt;li&gt;哲学上是 &amp;ldquo;If it hurts, do it more often&amp;rdquo;：痛苦的事要做小、做勤，风险切碎摊平，而不是攒起来赌一次大的；&lt;/li&gt;
&lt;li&gt;每次提交产出可发布候选；同一构建产物逐级晋升，环境差异靠外部配置注入；&lt;/li&gt;
&lt;li&gt;测试要金字塔不要冰淇淋筒：单元测试打底，验收测试讲业务语言，UI 测试最少；&lt;/li&gt;
&lt;li&gt;主干开发 + 特性开关替代长寿命分支，把&amp;quot;部署&amp;quot;和&amp;quot;发布&amp;quot;解耦；&lt;/li&gt;
&lt;li&gt;蓝绿部署和金丝雀发布把回滚能力内建在发布机制里；数据库变更走 expand-contract，每步向前向后兼容；&lt;/li&gt;
&lt;li&gt;配置和环境定义全部进版本控制，环境朝&amp;quot;可重建的牲畜&amp;quot;方向走；&lt;/li&gt;
&lt;li&gt;发布是业务决策，技术团队的职责是让那个按钮随时可按、按了能回；&lt;/li&gt;
&lt;li&gt;收尾的同构关系：客户开发是市场侧的小步试错，持续交付是工程侧的小步试错，精益创业把两者拧成一股。&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    <item>
      <title>《Unix 编程艺术》笔记：无为而治的操作系统版——不写多余的代码</title>
      <link>https://ftmao.com/blog/bookstore/art-of-unix-programming-notes/</link>
      <pubDate>Tue, 08 Aug 2023 10:40:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/bookstore/art-of-unix-programming-notes/</guid>
      <description>&lt;h1 id=&#34;unix-编程艺术笔记无为而治的操作系统版不写多余的代码&#34;&gt;《Unix 编程艺术》笔记：无为而治的操作系统版——不写多余的代码&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;目标读者：写过一两年代码、开始困惑&amp;quot;为什么有的系统越改越烂&amp;quot;的程序员。&lt;br&gt;
参考书籍：Eric S. Raymond《The Art of Unix Programming》（TAOUP，2003）&lt;br&gt;
阅读目标：把 Unix 哲学从&amp;quot;背口诀&amp;quot;变成&amp;quot;做决策的直觉&amp;quot;，知道什么时候&lt;strong&gt;不写代码&lt;/strong&gt;才是正确答案。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;0-写在前面这本书其实在讲无为&#34;&gt;0. 写在前面：这本书其实在讲&amp;quot;无为&amp;quot;&lt;/h2&gt;
&lt;p&gt;老子的&amp;quot;无为而治&amp;quot;常被误解成&amp;quot;什么都不做&amp;quot;。它的真实意思是：&lt;strong&gt;不妄为、不多为、让事物按自己的规律运转，治理者只搭最小的框架&lt;/strong&gt;。这篇博客的姊妹篇（道家经典笔记）里聊过这个，但当时我没想到，技术世界里有一套哲学和它是镜像关系——Unix 哲学。&lt;/p&gt;
&lt;p&gt;《Unix 编程艺术》这本书表面上是讲操作系统历史、讲工具、讲惯例，但通读下来，它的主线只有一句话：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;好的设计，是让每样东西只做一件事、把它做好，然后靠组合解决大问题——你写的代码越少，系统越长寿。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这就是&amp;quot;无为&amp;quot;的操作系统版：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;无为 ≠ 不写代码，而是&lt;strong&gt;不写多余的代码&lt;/strong&gt;（吝啬原则、经济原则）；&lt;/li&gt;
&lt;li&gt;无为而治 ≠ 没有规则，而是&lt;strong&gt;规则极少且稳定，万物在规则下自由组合&lt;/strong&gt;（组合原则、文本流接口）；&lt;/li&gt;
&lt;li&gt;为而不恃 ≠ 没有接口，而是&lt;strong&gt;接口低调透明，不强迫用户感知它&lt;/strong&gt;（透明原则、缄默原则）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;后面所有的原则、公案、争论，都是这一条主线的展开。先记住主线，再看细节，就不会把 17 条原则背成口诀表。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;1-一句话理解-unix-哲学&#34;&gt;1. 一句话理解 Unix 哲学&lt;/h2&gt;
&lt;p&gt;关于 Unix 哲学有很多版本的一句话总结，流传最广的是 Doug McIlroy（管道的发明人）在 1978 年《Bell System Technical Journal》里写的那段：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;让每个程序只做好一件事。要做新功能，就写个新程序，而不是给旧程序加&amp;quot;特性&amp;quot;把旧程序搞复杂。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;再加两句配套的：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;让每个程序的输出，都能成为另一个（还没写出来的）程序的输入。不要因为无关信息把输出搞得花里胡哨。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;以及后世程序员最爱引用的简版：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Do one thing and do it well.（做一件事，把它做好。）&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;注意 McIlroy 原话里最关键、也最容易被忽略的半句：&lt;strong&gt;&amp;ldquo;要做新功能，就写个新程序&amp;rdquo;&lt;/strong&gt;。这不是审美洁癖，而是工程判断——往旧程序里加功能，会把原本干净的工具变成一坨没人敢动的遗留代码。这条在后面 &lt;code&gt;cat -v&lt;/code&gt; 之争里会血淋淋地再出现一次。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;2-十七原则全景哪些是宪法哪些是实施细则&#34;&gt;2. 十七原则全景：哪些是&amp;quot;宪法&amp;quot;，哪些是&amp;quot;实施细则&amp;quot;&lt;/h2&gt;
&lt;p&gt;TAOUP 第一章把 Unix 哲学整理成了 17 条原则。我不打算逐条复述（那是目录，不是消化），先给一张全景图，再挑出真正管用的五条深讲。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;mindmap
  root((Unix哲学&amp;lt;br/&amp;gt;无为=不写多余代码))
    核心五条
      简单原则
        能简单就别复杂
        KISS
      组合原则
        程序互相连接
        文本流是通用接口
      透明原则
        一眼看穿在干嘛
        可调试可维护
      缄默原则
        没话就别说话
        输出即接口
      经济原则
        程序员时间最贵
        宁写一次脚本不重复劳动
    设计姿态
      模块原则
        清晰接口的简洁部件
      分离原则
        策略与机制分离
        接口与引擎分离
      吝啬原则
        小即是美
        除非必要别写大程序
    工程纪律
      清晰原则
        清晰胜过机巧
      稳健原则
        稳健源于透明加简单
      补救原则
        出错要吵要死得早
      优化原则
        先求跑通再求跑快
    生态智慧
      表示原则
        把知识叠进数据
      通俗原则
        最小惊讶原则
      生成原则
        能生成就不手写
      多样原则
        拒绝唯一正确方式
      扩展原则
        为将来留余地&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;21-哪些是宪法&#34;&gt;2.1 哪些是宪法？&lt;/h3&gt;
&lt;p&gt;17 条不是平权的。我自己的判断（也是 Raymond 书里的隐含排序）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;宪法级&lt;/strong&gt;：简单、组合、分离。这三条决定系统的基本骨架，违反了就救不回来。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;日常纪律级&lt;/strong&gt;：透明、缄默、吝啬、经济、清晰、补救。这些决定你每天写代码的手感。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;生态智慧级&lt;/strong&gt;：表示、生成、多样、扩展、优化、通俗、稳健。这些决定系统活十年之后的样子。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;下面只深讲五条最核心、也最容易被违反的。&lt;/p&gt;
&lt;h3 id=&#34;22-简单原则simplicity能简单就别复杂&#34;&gt;2.2 简单原则（Simplicity）：能简单就别复杂&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;原文：Design for simplicity; add complexity only where you must.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这是所有原则的根。复杂软件的问题不是&amp;quot;难写&amp;quot;，而是&lt;strong&gt;难改、难测、难理解&lt;/strong&gt;——复杂度是利息，每次改动都在还息。&lt;/p&gt;
&lt;p&gt;程序员最常见的自欺是：&amp;ldquo;这个功能以后肯定用得上，先设计成通用的。&amp;ldquo;结果通用框架写好了三年，那个&amp;quot;以后&amp;quot;从没来过，而每个新人都得先学懂这个没人用的框架才能干活。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;避坑指南&lt;/strong&gt;：当你设计一个系统需要超过一页纸才能向新同事解释清楚时，先怀疑设计，而不是怀疑新同事。&lt;/p&gt;
&lt;h3 id=&#34;23-组合原则composition程序要能被接起来&#34;&gt;2.3 组合原则（Composition）：程序要能被&amp;quot;接&amp;quot;起来&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;原文：Design programs to be connected with other programs.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这条是 Unix 真正的独创，也是它和同时代其他操作系统最大的区别。别的系统里，程序是一个个孤岛，各干各的；Unix 里的程序天生设计成&lt;strong&gt;可以被串起来&lt;/strong&gt;——输入是文本流，输出也是文本流，于是任何两个程序都能被一根管道接起来，哪怕写第一个程序的人根本不知道第二个程序的存在。&lt;/p&gt;
&lt;p&gt;这条原则的威力在第 4 节的 McIlroy 公案里会展现得淋漓尽致。先记住一个推论：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;一个程序如果不能被组合，它的能力上限就是它自己的代码量；一个能被组合的程序，能力上限是整个系统里所有程序的能力之积。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;24-透明原则transparency一眼看穿系统在干嘛&#34;&gt;2.4 透明原则（Transparency）：一眼看穿系统在干嘛&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;原文：Design for visibility to make inspection and debugging easier.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;透明性的标准是：&lt;strong&gt;系统的行为能不能被一个普通工程师&amp;quot;看出来&amp;rdquo;，而不是&amp;quot;猜出来&amp;rdquo;。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;反面教材：配置写在一个神秘的二进制数据库里、错误码只有厂商文档能查、出了问题只能重启看运气。正面教材：配置文件是文本、日志是人话、&lt;code&gt;strace&lt;/code&gt; 一看就知道进程在干什么。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;工程连接&lt;/strong&gt;：现在面试常考的&amp;quot;可观测性&amp;quot;（observability）——metrics、tracing、structured logging——本质上就是透明原则在云原生时代的重新发明。50 年前 Unix 用文本和管道做的事，今天我们用 OpenTelemetry 再做一遍。&lt;/p&gt;
&lt;h3 id=&#34;25-缄默原则silence没话就闭嘴&#34;&gt;2.5 缄默原则（Silence）：没话就闭嘴&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;原文：When a program has nothing surprising to say, it should say nothing.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这条最反直觉，新手程序员最爱犯：程序跑成功了，打印一行&amp;quot;操作成功完成！&amp;quot;；循环处理一万条数据，每条打一行进度。&lt;/p&gt;
&lt;p&gt;为什么这是错的？因为 Unix 程序的&lt;strong&gt;输出是接口&lt;/strong&gt;。&lt;code&gt;ls | wc -l&lt;/code&gt; 能数出目录里有几个文件，前提是 &lt;code&gt;ls&lt;/code&gt; 不会在末尾加一行&amp;quot;共 42 个文件，查询成功！&amp;quot;。一句多余的人话，对人是客气，对下游程序是脏数据。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;工程连接&lt;/strong&gt;：这条在今天的对应物是日志纪律——INFO 级别不该打印&amp;quot;一切正常&amp;quot;的心跳废话，告警不该在无事发生时刷屏。告警疲劳（alert fatigue）就是缄默原则被违反的现代版本：狼来了喊一千次，真的狼来了没人理。&lt;/p&gt;
&lt;h3 id=&#34;26-经济原则economy程序员的时间比机器的时间贵&#34;&gt;2.6 经济原则（Economy）：程序员的时间比机器的时间贵&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;原文：Programmer time is expensive; conserve it in preference to machine time.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;1970 年代机器慢、人便宜，这条原则在当时是革命性的；今天机器快得离谱、工程师年薪以十万计，这条原则更加正确。推论是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;能用 shell 脚本十分钟糊出来的临时工具，就别开 IDE 建工程写三天；&lt;/li&gt;
&lt;li&gt;能用高一级的语言（当年是 C vs 汇编，今天是 Python vs C++）表达，就别用低一级语言省那点机器时间；&lt;/li&gt;
&lt;li&gt;机器闲着的每一秒都不值钱，你加班的每一小时都很值钱。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这条原则是&amp;quot;无为&amp;quot;最直接的落地：&lt;strong&gt;能不做就不做，能少做就少做，把省下来的时间花在那件真正值得做的事上。&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;3-管道与过滤器unix-哲学的物理化身&#34;&gt;3. 管道与过滤器：Unix 哲学的物理化身&lt;/h2&gt;
&lt;h3 id=&#34;31-一夜诞生的管道&#34;&gt;3.1 一夜诞生的管道&lt;/h3&gt;
&lt;p&gt;管道（&lt;code&gt;|&lt;/code&gt;）的故事是软件史上最著名的&amp;quot;一夜奇迹&amp;quot;之一，值得完整讲一遍：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;1964 年&lt;/strong&gt;，Doug McIlroy 就在贝尔实验室的内部备忘录里写下了类似的想法：&amp;ldquo;我们应该有某种方法，能把程序像花园水管一样接起来——把一段数据拧进另一段。&amp;quot;（&amp;ldquo;We should have some ways of coupling programs like garden hose.&amp;quot;）&lt;/li&gt;
&lt;li&gt;想法提了快十年，一直没人实现。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;1973 年的一天&lt;/strong&gt;，McIlroy 又一次向 Ken Thompson 提起这个想法。Thompson 当晚回家，&lt;strong&gt;一夜之间修改了 Unix 内核和 shell&lt;/strong&gt;，把管道做了出来，顺手改了一批工具让它们能配合管道工作。&lt;/li&gt;
&lt;li&gt;第二天早上，管道就成了 Unix 的永久居民。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;McIlroy 后来回忆，那是 Unix 历史上最重要的时刻之一——一个想了十年的概念，被一个理解它的人用一晚上变成了现实。这个故事同时也是经济原则的最佳注脚：&lt;strong&gt;想清楚了再动手，动手就是一夜的事；没想清楚的团队，开会开十年。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&#34;32-文本流最简陋也最长寿的通用接口&#34;&gt;3.2 文本流：最简陋也最长寿的通用接口&lt;/h3&gt;
&lt;p&gt;管道为什么能串起一切？因为所有程序约定了同一个数据格式：&lt;strong&gt;字节流，惯例上是按行组织的文本。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这个接口&amp;quot;简陋&amp;quot;到可笑——没有 schema、没有类型检查、没有版本号。但正因如此：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1970 年写的程序，输出可以被 2023 年写的程序消费，中间隔了半个世纪、几代程序员、无数硬件更迭；&lt;/li&gt;
&lt;li&gt;任何新程序只要读 stdin、写 stdout，立刻自动获得与系统里&lt;strong&gt;所有&lt;/strong&gt;工具组合的能力，不用跟谁谈判、不用签接口协议。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;对比一下同时代的其他方案：IBM 的作业控制语言 JCL 要你先定义数据集格式，各种 RPC 框架要你先对齐接口定义。它们都更&amp;quot;严谨&amp;rdquo;，也都更快地死掉了。文本流活了 50 多年，至今是你每天 &lt;code&gt;kubectl get pods | grep xxx | awk &#39;{print $1}&#39;&lt;/code&gt; 时正在用的东西。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;一句话理解&lt;/strong&gt;：文本流是接口设计里的&amp;quot;中庸之道&amp;rdquo;——结构刚好够工具处理（按行、按空格分列），又松散到谁都能产生、谁都能消费。&lt;/p&gt;
&lt;h3 id=&#34;33-过滤器模式把程序写成水管中间的一截&#34;&gt;3.3 过滤器模式：把程序写成水管中间的一截&lt;/h3&gt;
&lt;p&gt;管道催生了一种程序形态：&lt;strong&gt;过滤器（filter）&lt;/strong&gt;——从 stdin 读，变换一下，往 stdout 写。&lt;code&gt;grep&lt;/code&gt;、&lt;code&gt;sort&lt;/code&gt;、&lt;code&gt;uniq&lt;/code&gt;、&lt;code&gt;awk&lt;/code&gt;、&lt;code&gt;sed&lt;/code&gt;、&lt;code&gt;tr&lt;/code&gt; 全是过滤器。&lt;/p&gt;
&lt;p&gt;过滤器的可怕之处在于&lt;strong&gt;乘法效应&lt;/strong&gt;：系统里有 N 个过滤器，理论上就有 N! 种串法（当然实际有意义的少得多，但数量级是对的）。每新增一个过滤器，系统的总能力不是 +1，而是 ×N。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    A[&amp;#34;输入: 任意文本&amp;lt;br/&amp;gt;(文件/网络/进程输出)&amp;#34;] --&amp;gt; B[&amp;#34;过滤器1&amp;lt;br/&amp;gt;grep/sed/tr&amp;#34;]
    B --&amp;gt; C[&amp;#34;过滤器2&amp;lt;br/&amp;gt;sort/awk&amp;#34;]
    C --&amp;gt; D[&amp;#34;过滤器3&amp;lt;br/&amp;gt;uniq/wc/head&amp;#34;]
    D --&amp;gt; E[&amp;#34;输出: 依然是文本&amp;lt;br/&amp;gt;可继续被消费&amp;#34;]
    B -. &amp;#34;每个程序只做一件事&amp;#34; .-&amp;gt; B
    C -. &amp;#34;接口统一为文本流&amp;#34; .-&amp;gt; C
    D -. &amp;#34;组合出无限可能&amp;#34; .-&amp;gt; D&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;工程连接&lt;/strong&gt;：这个模式的后代到处都是——MapReduce 的 map/reduce 是大规模版过滤器，Kafka 的 stream processing 是分布式版管道，React 的数据流、shell 出身的 Unix 老兵们设计的 CI/CD pipeline，全是同一张脸。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;4-经典公案六行-shell-挑战-knuth&#34;&gt;4. 经典公案：六行 shell 挑战 Knuth&lt;/h2&gt;
&lt;p&gt;这是软件史上我最喜欢的公案，没有之一，必须完整讲。&lt;/p&gt;
&lt;h3 id=&#34;41-背景bentley-出题knuth-接招&#34;&gt;4.1 背景：Bentley 出题，Knuth 接招&lt;/h3&gt;
&lt;p&gt;1986 年，Jon Bentley 在《Communications of the ACM》的&amp;quot;Programming Pearls&amp;quot;专栏出了一道题：&lt;strong&gt;给定一个文本文件和一个整数 N，输出出现频率最高的前 N 个单词及其次数。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;出题的目的不是真缺这个功能，而是想展示当时最时髦的编程方法论。Bentley 请来了 Donald Knuth——图灵奖得主、《计算机程序设计艺术》作者——用他发明的**文学编程（literate programming）**来解答。&lt;/p&gt;
&lt;p&gt;Knuth 交了一份大约&lt;strong&gt;十页&lt;/strong&gt;的 WEB 程序（Pascal 代码与散文交织），精心设计了哈希表、用 trie 变体做词频统计，数据结构精巧、行文优雅，是文学编程的样板戏。&lt;/p&gt;
&lt;h3 id=&#34;42-mcilroy-的评论六行管道&#34;&gt;4.2 McIlroy 的评论：六行管道&lt;/h3&gt;
&lt;p&gt;Bentley 随后请 Doug McIlroy 写评论。McIlroy 先客气地称赞了 Knuth 的程序，然后平静地指出：这个任务，用 Unix 工具六行就能做完，而且更好——因为每一行都是久经考验的现成工具，没有一行是新写的、可能有 bug 的代码：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;tr -cs A-Za-z &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;\n&amp;#39;&lt;/span&gt; |
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;tr A-Z a-z |
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sort |
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;uniq -c |
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sort -rn |
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sed &lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;1&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;q&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;逐行翻译：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;tr -cs A-Za-z &#39;\n&#39;&lt;/code&gt;：把所有非字母字符换成换行——文本变成&amp;quot;每行一个词&amp;quot;；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tr A-Z a-z&lt;/code&gt;：全部转小写——&amp;ldquo;The&amp;quot;和&amp;quot;the&amp;quot;算同一个词；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sort&lt;/code&gt;：排序——相同的词现在挨在一起；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;uniq -c&lt;/code&gt;：合并相邻重复行并计数——每行变成&amp;quot;次数 单词&amp;rdquo;；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sort -rn&lt;/code&gt;：按数字逆序排——高频词浮到顶部；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sed ${1}q&lt;/code&gt;：只保留前 N 行（N 从命令行参数来）。&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    T[&amp;#34;原始文本&amp;lt;br/&amp;gt;The quick brown fox&amp;lt;br/&amp;gt;the lazy dog&amp;#34;] --&amp;gt; S1[&amp;#34;tr: 拆成单词&amp;lt;br/&amp;gt;每行一个词&amp;#34;]
    S1 --&amp;gt; S2[&amp;#34;tr: 统一小写&amp;lt;br/&amp;gt;消除大小写干扰&amp;#34;]
    S2 --&amp;gt; S3[&amp;#34;sort: 排序&amp;lt;br/&amp;gt;相同单词聚到一起&amp;#34;]
    S3 --&amp;gt; S4[&amp;#34;uniq -c: 计数&amp;lt;br/&amp;gt;2 the&amp;lt;br/&amp;gt;1 brown&amp;lt;br/&amp;gt;1 dog&amp;#34;]
    S4 --&amp;gt; S5[&amp;#34;sort -rn: 按频次降序&amp;#34;]
    S5 --&amp;gt; S6[&amp;#34;sed Nq: 取前N名&amp;#34;]
    style S1 fill:#e8f4e8
    style S2 fill:#e8f4e8
    style S3 fill:#e8f4e8
    style S4 fill:#e8f4e8
    style S5 fill:#e8f4e8
    style S6 fill:#e8f4e8&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;43-这场公案到底说明了什么&#34;&gt;4.3 这场公案到底说明了什么&lt;/h3&gt;
&lt;p&gt;Knuth 本人输了吗？没有。他的哈希 trie 在大规模数据上确实更快，而且十页程序的初衷是展示文学编程，不是交付生产工具。这场公案真正的价值在于 McIlroy 的评论里那几句话的杀伤力：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Knuth 的程序里，每个数据结构都是新的、都需要正确性证明；shell 管道里没有一个数据结构是新的，sort 和 uniq 已经被全世界的人每天用了十几年。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这就是&amp;quot;无为&amp;quot;的极致：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;不写代码，就是最高的代码质量&lt;/strong&gt;——没有新写的数据结构，就没有新的 off-by-one 错误；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;组合现成工具，胜过手工打造精品&lt;/strong&gt;——文学编程追求的&amp;quot;像文学一样优雅的程序&amp;quot;，败给了&amp;quot;根本不用写程序&amp;quot;；&lt;/li&gt;
&lt;li&gt;顺便还赢了可读性：Knuth 的程序要读十页，McIlroy 的读十秒。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;每次我看到团队为一个&amp;quot;一次性数据统计需求&amp;quot;立项、排期、写微服务，我都会想起这六行 shell。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;5-机制与策略分离x-window-的世纪教训&#34;&gt;5. 机制与策略分离：X Window 的世纪教训&lt;/h2&gt;
&lt;p&gt;分离原则（Separation）说：&lt;strong&gt;把策略（policy）和机制（mechanism）分开；把接口和引擎分开。&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;机制&lt;/strong&gt;：能做什么——提供能力，比如&amp;quot;能在屏幕上画窗口、能接收鼠标点击&amp;quot;；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;策略&lt;/strong&gt;：该怎么做——决定行为，比如&amp;quot;窗口标题栏长什么样、点击关闭按钮是杀进程还是最小化&amp;quot;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;51-x-window把不表态写进架构&#34;&gt;5.1 X Window：把&amp;quot;不表态&amp;quot;写进架构&lt;/h3&gt;
&lt;p&gt;Unix 世界对这个原则最彻底的实践是 &lt;strong&gt;X Window System（X11）&lt;/strong&gt;，1984 年起源于 MIT。X 的设计者把&amp;quot;机制，而非策略&amp;quot;（mechanism, not policy）直接写成了项目格言。&lt;/p&gt;
&lt;p&gt;具体怎么分的：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TB
    subgraph 策略层[&amp;#34;策略层(可由用户替换)&amp;#34;]
        WM[&amp;#34;窗口管理器&amp;lt;br/&amp;gt;决定标题栏样式/焦点行为&amp;#34;]
        TK[&amp;#34;控件工具包&amp;lt;br/&amp;gt;决定按钮长什么样&amp;#34;]
    end
    subgraph 机制层[&amp;#34;机制层(X服务器)&amp;#34;]
        XS[&amp;#34;X Server&amp;lt;br/&amp;gt;只管画像素/收输入事件&amp;lt;br/&amp;gt;不关心&amp;#39;窗口&amp;#39;概念&amp;#34;]
    end
    APP[&amp;#34;应用程序&amp;#34;] --&amp;gt; TK
    WM --&amp;gt; XS
    TK --&amp;gt; XS
    XS -. &amp;#34;协议(X protocol)&amp;#34; .- APP&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;X 服务器&lt;strong&gt;故意不知道&lt;/strong&gt;窗口应该长什么样、标题栏该有几个按钮。它只提供画矩形、画字、传鼠标键盘事件的机制。所有&amp;quot;外观和行为&amp;quot;都推给了可替换的窗口管理器和工具包。&lt;/p&gt;
&lt;p&gt;结果是惊人的长寿和多样性：同一套 X 协议跑了近四十年，上面先后长出过 Motif、GTK、Qt 等无数风格迥异的桌面环境，X 服务器本身几乎不用变。机制稳定，策略自由演化——这就是&amp;quot;无为而治&amp;quot;的架构版：底层定最少的规矩，上层爱怎么长就怎么长。&lt;/p&gt;
&lt;h3 id=&#34;52-反面教材策略焊死在机制里&#34;&gt;5.2 反面教材：策略焊死在机制里&lt;/h3&gt;
&lt;p&gt;对比之下，很多 GUI 系统把&amp;quot;按钮必须长这样、窗口必须这样关闭&amp;quot;直接焊死在系统底层。短期看开箱即用，长期看：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;想换一种交互风格？改不动底层，只能外挂 hack；&lt;/li&gt;
&lt;li&gt;底层想升级？牵一发而动全身，一动就破坏所有应用。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;工程连接&lt;/strong&gt;：这个教训在今天依然天天上演——&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Kubernetes 的控制循环是机制，你的 CRD/Operator 是策略；&lt;/li&gt;
&lt;li&gt;HTTP 是机制，REST 风格是策略；&lt;/li&gt;
&lt;li&gt;编译器前端后端分离（LLVM 的 IR 是机制，各语言前端是策略）；&lt;/li&gt;
&lt;li&gt;你自己写的框架如果把&amp;quot;业务规则&amp;quot;和&amp;quot;执行引擎&amp;quot;揉在一个类里，就是在给三年后的自己埋雷。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;一句话理解&lt;/strong&gt;：机制回答&amp;quot;能不能&amp;quot;，策略回答&amp;quot;该不该&amp;quot;。把&amp;quot;该不该&amp;quot;的决定权留给离用户最近的那层。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;6-cat--v-之争小即是美的保卫战&#34;&gt;6. cat -v 之争：小即是美的保卫战&lt;/h2&gt;
&lt;h3 id=&#34;61-一只猫引发的血案&#34;&gt;6.1 一只猫引发的血案&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;cat&lt;/code&gt; 是 Unix 里最单纯的程序：把文件内容原样输出。它的名字来自 concatenate（拼接），&lt;code&gt;cat a b c &amp;gt; all&lt;/code&gt; 把三个文件拼成一个。就这么点功能，干净漂亮。&lt;/p&gt;
&lt;p&gt;后来 BSD（伯克利版 Unix）的开发者给 &lt;code&gt;cat&lt;/code&gt; 加了选项，比如 &lt;code&gt;-v&lt;/code&gt; 可以把不可见字符显示出来。听起来人畜无害，对吧？&lt;/p&gt;
&lt;p&gt;1983 年，Rob Pike 和 Brian Kernighan 在 USENIX 大会上发了一篇演讲，标题就很凶：&lt;strong&gt;《Program Design in the UNIX Environment》&lt;/strong&gt;，其中一节叫 &amp;ldquo;cat -v considered harmful&amp;rdquo;（化用 Dijkstra 的名篇）。核心论点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;cat&lt;/code&gt; 的职责是&lt;strong&gt;拼接字节流&lt;/strong&gt;，&amp;ldquo;可视化不可见字符&amp;quot;是&lt;strong&gt;另一件事&lt;/strong&gt;，属于另一个程序；&lt;/li&gt;
&lt;li&gt;一旦 &lt;code&gt;cat&lt;/code&gt; 开始长选项，它就会无限长下去——今天是 &lt;code&gt;-v&lt;/code&gt;，明天是显示行号、压缩空行……Unix 后来的 cat 果然长出了 &lt;code&gt;-n&lt;/code&gt;、&lt;code&gt;-s&lt;/code&gt;、&lt;code&gt;-E&lt;/code&gt;、&lt;code&gt;-T&lt;/code&gt; 一长串；&lt;/li&gt;
&lt;li&gt;正确做法是写一个专门的小程序做可视化（Pike 他们后来写了 &lt;code&gt;vis&lt;/code&gt;），让 &lt;code&gt;cat&lt;/code&gt; 保持单纯。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;62-这不是洁癖是防熵增&#34;&gt;6.2 这不是洁癖，是防熵增&lt;/h3&gt;
&lt;p&gt;回头看，Pike 他们争的从来不是 &lt;code&gt;cat&lt;/code&gt; 这一个程序，而是一个普适规律：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;软件的自然演化方向是功能蔓延（feature creep），而每一次&amp;quot;顺手加个选项&amp;quot;都在加速它。今天不加护栏，三年后它就会变成没人敢动的屎山。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;工程连接&lt;/strong&gt;：你身边一定有那个&amp;quot;本来是 200 行的小工具，三年后变成 8000 行、40 个命令行参数、没人知道所有参数组合行为&amp;quot;的内部程序。它就是一场输掉了的 cat -v 战争。McIlroy 那句话在这里闭环了：&lt;strong&gt;&amp;ldquo;要做新功能，就写个新程序，而不是给旧程序加特性。&amp;rdquo;&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&#34;63-小即是美small-is-beautiful&#34;&gt;6.3 小即是美（Small is Beautiful）&lt;/h3&gt;
&lt;p&gt;cat -v 之争背后的正面主张，后来被 Mike Gancarz 总结成一本书的名字：《Linux and the Unix Philosophy》里的核心口号——&lt;strong&gt;Small is Beautiful（小即是美）&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;小程序容易写、容易测、容易懂、容易替换；&lt;/li&gt;
&lt;li&gt;大程序每一处都是沉没成本，坏了一个角就全盘皆输；&lt;/li&gt;
&lt;li&gt;一个 200 行的小工具，用不顺手可以扔掉重写；一个 8000 行的怪兽，你只能供着它。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;7-worse-is-betterunix-为什么能赢&#34;&gt;7. Worse is Better：Unix 为什么能赢&lt;/h2&gt;
&lt;h3 id=&#34;71-gabriel-的著名论断&#34;&gt;7.1 Gabriel 的著名论断&lt;/h3&gt;
&lt;p&gt;1989 年前后，Lisp 黑客 Richard Gabriel 写了一篇著名文章（后收录于《Lisp: Good News, Bad News, How to Win Big》，1991），提出了一个让理想主义者抓狂的概念：&lt;strong&gt;Worse is Better（更糟的就是更好的）&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;他对比了两种设计哲学：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;MIT/Stanford 路线（&amp;ldquo;正确的事&amp;rdquo;，The Right Thing）&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;设计必须完整、正确、一致、优雅；&lt;/li&gt;
&lt;li&gt;接口的简洁性没有实现的正确性重要；&lt;/li&gt;
&lt;li&gt;代表：Lisp 机器、Scheme 的理想主义一脉。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;新泽西路线（Worse is Better，新泽西 = 贝尔实验室所在地）&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;实现的简单性优先于一切&lt;/strong&gt;，接口简单、正确性、一致性都要为它让路；&lt;/li&gt;
&lt;li&gt;功能做 50% 能跑，好过做 90% 但没人写完；&lt;/li&gt;
&lt;li&gt;代表：C 和 Unix。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Gabriel 的观察是：那个&amp;quot;更糟&amp;quot;的新泽西方案会赢，因为它&lt;strong&gt;像病毒一样可移植&lt;/strong&gt;——简单的东西容易实现、容易移植到任何机器上，先占领世界，再靠用户社区慢慢把它补齐。而&amp;quot;正确的事&amp;quot;因为太重、太贵、太难实现，永远停留在少数精英的实验室里。&lt;/p&gt;
&lt;h3 id=&#34;72-历史判卷gabriel-说对了&#34;&gt;7.2 历史判卷：Gabriel 说对了&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;C 语言有一堆&amp;quot;糟糕&amp;quot;的设计（缓冲区溢出、未定义行为），但它简单到一个人能写个编译器，于是 C 编译器铺满了地球上每一台机器；&lt;/li&gt;
&lt;li&gt;Unix 的系统调用接口远不如 Lisp 机器优雅，但 Unix 能跑在从 PDP-11 到超算的一切东西上；&lt;/li&gt;
&lt;li&gt;反观 Lisp 机器、各种&amp;quot;设计完美&amp;quot;的操作系统，坟头草已经三米高了。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;73-但要小心这不是给烂代码发通行证&#34;&gt;7.3 但要小心：这不是给烂代码发通行证&lt;/h3&gt;
&lt;p&gt;Gabriel 本人对这篇文章的态度其实很矛盾（他甚至用化名写过反驳自己的文章）。Worse is better 描述的是&lt;strong&gt;演化的规律&lt;/strong&gt;，不是&lt;strong&gt;你写烂代码的借口&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;它说的是&amp;quot;实现简单、先跑起来、让世界帮你完善&amp;quot;会赢；&lt;/li&gt;
&lt;li&gt;它&lt;strong&gt;不是&lt;/strong&gt;说&amp;quot;不测试、不处理错误、随便糊&amp;quot;会赢。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;一句话理解&lt;/strong&gt;：先交付一个简单到不可能失败的 60 分方案，让它在真实世界里进化；不要在象牙塔里憋一个永远交付不了的 100 分方案。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;8-无用之用shell-脚本的临时救赎&#34;&gt;8. 无用之用：shell 脚本的临时救赎&lt;/h2&gt;
&lt;p&gt;道家讲&amp;quot;无用之用，方为大用&amp;rdquo;——看起来没用的东西，往往有大用。Shell 脚本就是编程世界里的&amp;quot;无用之用&amp;quot;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;它&amp;quot;不入流&amp;quot;：没有类型系统、没有 IDE 补全、面试不考、简历上不好意思写；&lt;/li&gt;
&lt;li&gt;但它解决的是&lt;strong&gt;真实世界里最高频的那类问题&lt;/strong&gt;：一次性的、临时的、不值得立项的脏活。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;举几个我自己（和每个老 Unix 用户）每周都在做的事：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 找出日志里报错最多的 10 个接口&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;grep ERROR app.log | awk &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;{print $5}&amp;#39;&lt;/span&gt; | sort | uniq -c | sort -rn | head
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 把 300 个 .jpeg 文件批量重命名成 .jpg&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; f in *.jpeg; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt; mv &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$f&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;f%.jpeg&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;.jpg&amp;#34;&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 监控某个进程，挂了自动拉起（穷人版 supervisor）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; true; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt; ./myserver &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt; sleep 5; &lt;span style=&#34;color:#66d9ef&#34;&gt;done&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;这些脚本&amp;quot;丑陋&amp;quot;、&amp;ldquo;脆弱&amp;rdquo;、&amp;ldquo;不可维护&amp;rdquo;——都没错，但它们本来就是&lt;strong&gt;一次性用品&lt;/strong&gt;，写完跑完就扔。为它们搞工程化、写单元测试、上代码评审，恰恰违反了经济原则：程序员的时间比机器贵，也比&amp;quot;代码的体面&amp;quot;贵。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;关键判断力&lt;/strong&gt;在于分清楚两类问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;临时问题&lt;/strong&gt; → shell 糊一个，跑完即弃，这是&amp;quot;无为&amp;quot;的胜利；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;反复出现的问题&lt;/strong&gt; → 那就不是临时问题了，老老实实写成正经工具，别拿&amp;quot;反正临时用用&amp;quot;当借口让脚本混进生产系统核心路径。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;区分错了方向都很惨：给临时问题过度工程化是浪费生命，让临时脚本变成永久依赖是埋雷（无数公司的核心发布流程，起点都是某人&amp;quot;临时写一下&amp;quot;的 500 行 bash）。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;9-与无为而治的正式呼应&#34;&gt;9. 与&amp;quot;无为而治&amp;quot;的正式呼应&lt;/h2&gt;
&lt;p&gt;呼应一下开头，把这条镜像关系钉死：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;道家&lt;/th&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;Unix 哲学&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;无为 = 不妄为&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;不写多余的代码（吝啬/经济原则）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;治大国若烹小鲜&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;大系统由小工具组合而成（组合原则）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;道法自然&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;顺应文本流/管道这个&amp;quot;自然接口&amp;quot;，不发明私有协议&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;上善若水，水善利万物而不争&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;过滤器程序低调透明，只过数据不刷存在感（缄默原则）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;圣人无常心，以百姓心为心&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;机制不预设策略，把选择权留给用户（X Window）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;无用之用&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;shell 一次性脚本、简单的文本接口反而最长寿&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;但注意：&lt;strong&gt;这本书的主体是工程，不是玄学。&lt;/strong&gt; 道家给的是一个好用的直觉框架，真正支撑 Unix 哲学的是 50 年的工程证据——管道的胜利、文本流的长寿、cat -v 的沦陷、Lisp 机器的坟墓。直觉负责让你快速做出方向正确的决定，证据负责在你被质疑时撑住你。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;10-带走的清单&#34;&gt;10. 带走的清单&lt;/h2&gt;
&lt;p&gt;写代码或做设计之前，过一遍这十条（按 17 原则提炼后的实战版）：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;能不写就不写&lt;/strong&gt;：这个问题用现有工具组合能解决吗？（McIlroy 公案）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;一次只做一件事&lt;/strong&gt;：这个程序/类/函数，能用一句话说清职责吗？&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;输出即接口&lt;/strong&gt;：这个程序的输出，能被另一个还没写出来的程序直接消费吗？（缄默原则）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;用文本说事&lt;/strong&gt;：这个配置/日志/接口，是人能直接读的文本吗？（透明原则）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;机制和策略分开了吗&lt;/strong&gt;：我写的这层，是提供能力，还是替用户做决定？（X Window 教训）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;先跑起来再说&lt;/strong&gt;：现在的版本是不是简单到不可能失败？（Worse is better）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;这是临时问题吗&lt;/strong&gt;：是 → 脚本糊；不是 → 正经写。别搞反。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;我在加功能还是加复杂度&lt;/strong&gt;：这个&amp;quot;顺手加的小功能&amp;quot;，是不是下一个 cat -v？&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;复杂度在哪一层&lt;/strong&gt;：不得不复杂时，复杂度被隔离在最小范围了吗？&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;删掉一半会怎样&lt;/strong&gt;：如果砍掉一半功能，核心用户还在吗？在 → 砍。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;11-避坑指南对-unix-哲学最常见的四种误用&#34;&gt;11. 避坑指南：对 Unix 哲学最常见的四种误用&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;坑一：把 KISS 当懒惰的挡箭牌。&lt;/strong&gt; &amp;ldquo;简单&amp;quot;指的是设计和实现的简单，不是&amp;quot;不做需求、不处理边界情况&amp;rdquo;。Knuth 的词频程序虽然输给了六行 shell，但它处理的性能边界是 shell 版处理不了的——该复杂的地方必须复杂。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;坑二：把 worse is better 当烂代码许可证。&lt;/strong&gt; 它描述的是&amp;quot;简单实现先活下来再进化&amp;quot;的演化规律，不是&amp;quot;别写测试、别管错误处理&amp;quot;。60 分方案的前提是&lt;strong&gt;边界清晰、失败模式明确&lt;/strong&gt;，不是糊。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;坑三：把&amp;quot;小即是美&amp;quot;理解成微服务越多越好。&lt;/strong&gt; 微服务拆分如果只分了部署、没分清楚接口，得到的是分布式屎山。组合原则的前提是&lt;strong&gt;每个部件接口干净&lt;/strong&gt;，否则 N 个服务只有 N 倍的运维痛苦，没有组合红利。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;坑四：用 shell 糊生产核心。&lt;/strong&gt; 一次性脚本的正确归宿是跑完即弃（或转正为正经工具）。让 500 行没人看懂的 bash 长在发布流程的心脏上，是把&amp;quot;无用之用&amp;quot;用成了&amp;quot;无用之害&amp;quot;。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;12-结语&#34;&gt;12. 结语&lt;/h2&gt;
&lt;p&gt;《Unix 编程艺术》出版于 2003 年，讲的是 1969 年开始的故事，但它预言对了此后二十年几乎所有的技术走向：文本协议（HTTP/JSON）打败二进制协议，组合式工具（容器、K8s、CI/CD pipeline）打败单体巨兽，&amp;ldquo;简单实现先跑起来&amp;rdquo;（Python、Go 的设计取向）打败&amp;quot;正确的事&amp;quot;。&lt;/p&gt;
&lt;p&gt;回到那句主线：&lt;strong&gt;无为 = 不写多余的代码。&lt;/strong&gt; 最高明的程序员不是写得最快的人，而是最清楚哪行代码不该存在的人。就像老子说的，为学日益，为道日损——学技术是不断加法，懂设计是不断减法。&lt;/p&gt;
&lt;p&gt;这本书值得每五年重读一遍，因为你对&amp;quot;哪行代码不该存在&amp;quot;的判断力，会随踩坑数量逐年升级。&lt;/p&gt;
</description>
    </item>
    <item>
      <title>《人月神话》笔记：Brooks 用 OS/360 的学费换来的软件工程第一性原理</title>
      <link>https://ftmao.com/blog/bookstore/mythical-man-month-book-notes/</link>
      <pubDate>Tue, 08 Aug 2023 10:40:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/bookstore/mythical-man-month-book-notes/</guid>
      <description>&lt;h1 id=&#34;人月神话笔记brooks-用-os360-的学费换来的软件工程第一性原理&#34;&gt;《人月神话》笔记：Brooks 用 OS/360 的学费换来的软件工程第一性原理&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;目标读者：带过项目、估过排期、经历过&amp;quot;老板说加人就能赶上&amp;quot;的工程师和工程管理者。
参考书籍：Frederick P. Brooks Jr.《人月神话》（The Mythical Man-Month，1975 年初版，1995 年 20 周年纪念版），含 1986 年论文《没有银弹》（No Silver Bullet）。
阅读目标：不是复述一本 50 年前的书，而是把 Brooks 用 IBM OS/360 项目的巨额学费换来的几条&amp;quot;软件工程第一性原理&amp;quot;蒸馏成今天还能直接用的判断准则。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;一焦油坑这本书从哪里长出来的&#34;&gt;一、焦油坑：这本书从哪里长出来的&lt;/h2&gt;
&lt;p&gt;书的第一章叫「焦油坑」（The Tar Pit），Brooks 用了一个古生物学意象：拉布雷亚沥青坑里，猛犸、剑齿虎、恐龙——越是强壮的野兽，挣扎得越猛烈，陷得越深。&lt;strong&gt;大型系统编程就是这样一个焦油坑：没有哪支队伍特别笨，但大家都在里面挣扎，而且越挣扎陷得越深。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这句话不是文学修辞，是作者的身份声明。Brooks 是 IBM System/360 大型机项目的经理，随后又是其操作系统 &lt;strong&gt;OS/360&lt;/strong&gt; 的项目经理——这是 1960 年代人类历史上最大的软件项目之一，几千人年、超支数倍、进度失控、bug 成山。1964 年 IBM 老板小沃森问他项目为什么管成这样，他答不上来；几个月后他离开 IBM 去北卡大学建计算机系，把这次失败翻来覆去想了十年，1975 年写出这本书。&lt;/p&gt;
&lt;p&gt;所以读这本书的正确姿势是：&lt;strong&gt;这不是一个管理学教授在总结别人的案例，是一个搞砸过史上最大软件项目的人在写尸检报告。&lt;/strong&gt; 书里每一条论断背后，都是 OS/360 真金白银的教训。这也是为什么这本书快 50 年了还不过时——硬件换了五代，语言换了几轮，但&amp;quot;人怎么组织起来写大软件&amp;quot;这个问题的底层结构，几乎没变。&lt;/p&gt;
&lt;p&gt;全书论点可以用一句话串起来：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;软件项目的失败，绝大多数不是技术失败，而是对&amp;quot;编程这项活动的本质&amp;quot;估计错误的失败——错估了沟通成本、错估了复杂度的来源、错估了设计与实现的相对价值。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;二人月神话人月是不可互换的&#34;&gt;二、人月神话：人月是不可互换的&lt;/h2&gt;
&lt;p&gt;书名即论点。&lt;strong&gt;人月（man-month）——用人乘以月来度量工作量——是一个神话，因为它暗含一个错误前提：人和月可以互换。&lt;/strong&gt; 10 个人干 1 个月，和 1 个人干 10 个月，不是一回事。&lt;/p&gt;
&lt;p&gt;Brooks 的论证是一个漂亮的分类学，按任务的&lt;strong&gt;可分解性&lt;/strong&gt;拆开：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;完全可分解、无需沟通的任务&lt;/strong&gt;：比如收麦子、筛数据。这类任务人月确实可互换，人加倍时间减半。可惜软件里几乎没有这种任务。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可分解但需要沟通的任务&lt;/strong&gt;：大部分编码工作属于此类。增加人手，沟通成本随之而来，收益开始打折。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可分解但需要复杂沟通与再培训的任务&lt;/strong&gt;：沟通成本开始吞噬新增人手带来的产能。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;完全不可分解、有严格次序依赖的任务&lt;/strong&gt;：比如调试（本质上是顺序的），比如生孩子——九个月，几个母亲也压缩不了。软件里的系统设计和集成调试大量属于此类。&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    T[&amp;#34;软件任务&amp;#34;] --&amp;gt; Q{&amp;#34;能否分解为&amp;lt;br/&amp;gt;无需沟通的子任务?&amp;#34;}
    Q --&amp;gt;|&amp;#34;能(极少: 收麦子型)&amp;#34;| A[&amp;#34;人月近似可互换&amp;lt;br/&amp;gt;人加倍, 工期减半&amp;#34;]
    Q --&amp;gt;|&amp;#34;不能(绝大多数)&amp;#34;| Q2{&amp;#34;主要成本是什么?&amp;#34;}
    Q2 --&amp;gt;|&amp;#34;沟通与协调&amp;#34;| B[&amp;#34;加人收益递减&amp;lt;br/&amp;gt;沟通路径数 = n乘n减1再除2&amp;#34;]
    Q2 --&amp;gt;|&amp;#34;严格次序依赖&amp;lt;br/&amp;gt;设计、调试、集成&amp;#34;| C[&amp;#34;加人完全无效&amp;lt;br/&amp;gt;九个月生不出孩子&amp;#34;]
    style C fill:#ffcccc
    style A fill:#e8f5e9&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;沟通成本那条值得算一笔小账：n 个人两两沟通，路径数是 n(n-1)/2。3 个人是 3 条，7 个人是 21 条，10 个人是 45 条。&lt;strong&gt;人数翻倍，沟通路径数大约翻两番。&lt;/strong&gt; 如果每次协作都要开会、对齐、等回复，人越多，每个人写代码的时间占比越低。这就是为什么 Brooks 说&amp;quot;用人月作为度量，等于默认了一个只在收麦子时成立的假设&amp;quot;。&lt;/p&gt;
&lt;p&gt;这条原理的一个直接推论，常常被忽略：&lt;strong&gt;进度落后的项目，往往不是人太少，而是人太多&lt;/strong&gt;——沟通结构已经把产能吃掉了，这时候减人反而可能提速。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;三brooks-定律向延期的项目加人只会让它更晚&#34;&gt;三、Brooks 定律：向延期的项目加人，只会让它更晚&lt;/h2&gt;
&lt;p&gt;这是全书最出名的一句话，1975 年提出，后来被叫成 Brooks 定律（Brooks&amp;rsquo;s Law）：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Adding manpower to a late software project makes it later.（向一个已经延期的软件项目增加人手，只会让它更晚。）&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;为什么&amp;quot;加人赶工&amp;quot;这个管理者最直觉的动作是错的？Brooks 拆了三层成本：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;培训成本&lt;/strong&gt;：新人不能立刻干活。他们要先学项目的目标、架构、既有代码、开发流程——而教他们的人，正是团队里本来在干活的老手。老手的时间被培训占用，团队短期产能不升反降。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;沟通成本&lt;/strong&gt;：就是上一节的 n(n-1)/2。加人后，任务要重新划分、接口要重新对齐，原本两个人口头约定的事，现在要写文档、开会。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;任务重划分成本&lt;/strong&gt;：很多任务被打断重拆时，边界上的工作要重做；有些任务根本拆不动（次序依赖的部分），能拆出去的部分往往是最不需要人的部分。&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    subgraph 加人前
        A1[&amp;#34;团队产能 100%&amp;lt;br/&amp;gt;全部用于推进项目&amp;#34;]
    end
    subgraph 加人后短期
        B1[&amp;#34;老手产能下降&amp;lt;br/&amp;gt;抽时间培训新人&amp;#34;] --&amp;gt; B3[&amp;#34;净产能低于加人前&amp;#34;]
        B2[&amp;#34;新人产能为负&amp;lt;br/&amp;gt;提问、犯错、需要返工&amp;#34;] --&amp;gt; B3
        B4[&amp;#34;沟通路径数平方增长&amp;lt;br/&amp;gt;对齐成本吞噬产出&amp;#34;] --&amp;gt; B3
    end
    subgraph 加人后长期
        C1[&amp;#34;若项目不取消&amp;lt;br/&amp;gt;数月后产能才可能恢复&amp;lt;br/&amp;gt;但死线已过&amp;#34;]
    end
    A1 -.-&amp;gt;|&amp;#34;管理者直觉: 产能应线性上升&amp;#34;| B1
    B3 --&amp;gt; C1
    style B3 fill:#ffcccc&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;书里还有一个配套观察，杀伤力同样大：&lt;strong&gt;项目是怎样延期一年的？一天一天延的。&lt;/strong&gt;（How does a project get to be a year late? One day at a time.）大灾难不是突然降临的，是每天落后一点点、每次例会都&amp;quot;下周能追上&amp;quot;累积出来的。所以 Brooks 主张里程碑必须&lt;strong&gt;具体、锋利、公开&lt;/strong&gt;——&amp;ldquo;编码完成 90%&amp;ldquo;不是里程碑，是遮羞布；&amp;ldquo;模块通过全部单元测试并合入主库&amp;quot;才是。&lt;/p&gt;
&lt;p&gt;对今天的映射几乎不用翻译：每个 deadline 前两周&amp;quot;借调支援&amp;quot;的场景，都是 Brooks 定律的现场重演。正确的动作恰恰相反——&lt;strong&gt;砍掉 20% 的范围，比增加 20% 的人有效得多。&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;四概念完整性与外科手术队伍&#34;&gt;四、概念完整性与外科手术队伍&lt;/h2&gt;
&lt;h3 id=&#34;41-最重要的系统属性不是功能是概念完整性&#34;&gt;4.1 最重要的系统属性不是功能，是概念完整性&lt;/h3&gt;
&lt;p&gt;Brooks 认为，一个系统给用户呈现的东西里，&lt;strong&gt;概念完整性（conceptual integrity）是最重要的品质&lt;/strong&gt;——系统各处必须体现同一套设计哲学，一致、连贯、正交。用户学透三个概念就能推测出三十个行为的系统，远好过每个功能都自说自话的系统。&lt;/p&gt;
&lt;p&gt;但概念完整性有一个组织上的代价：&lt;strong&gt;它无法由委员会设计出来。&lt;/strong&gt; 民主设计出来的系统，是每个聪明人都塞进了自己最爱的特性的系统——功能很多，灵魂没有。所以 Brooks 给了一个政治不正确但极其诚实的结论：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;概念完整性要求设计出自一个人，或极少数同心同德的人。为了完整性，必须接受贵族专制（aristocracy），放弃民主。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这不等于独裁者包揽一切。Brooks 的关键区分是：&lt;strong&gt;体系结构（architecture）与实现（implementation）分离&lt;/strong&gt;。架构师定义&amp;quot;系统对用户是什么样&amp;rdquo;——接口、语义、概念模型；大量实现者在架构约束下尽情发挥创造力。OS/360 的教训是：架构师也必须克制，架构文档里不该规定任何&amp;quot;实现细节&amp;rdquo;，否则就是把实现者的创造力抢走、把架构变成瓶颈。&lt;/p&gt;
&lt;p&gt;Unix 是这条原则最著名的正面样本（Brooks 自己也拿它举例）：一切皆文件、程序是过滤器、文本流为接口——概念少到极致，组合力大到极致。&lt;/p&gt;
&lt;h3 id=&#34;42-外科手术队伍小团队怎么做大事&#34;&gt;4.2 外科手术队伍：小团队怎么做大事&lt;/h3&gt;
&lt;p&gt;如果人少才有一致性，人少又怎么完成大项目？Brooks 的回答是全书我最喜欢的组织设计：&lt;strong&gt;外科手术队伍（surgical team）&lt;/strong&gt;——不是&amp;quot;人人都是全才&amp;quot;的平等团队，而是像一台外科手术那样，一个主刀，其余所有人都是为了让主刀的每一分钟都高效而存在。&lt;/p&gt;
&lt;p&gt;10 人编制大致是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;外科医生（首席程序员）&lt;/strong&gt;：亲手定义功能与性能规格，亲手写核心代码，对系统负全责。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;副手（copilot）&lt;/strong&gt;：能替代外科医生做任何事，是他的讨论对象、保险丝和备份，但不抢方向盘。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;管理员&lt;/strong&gt;：处理钱、人、场地、设备这些非技术事务——外科医生不该为报销分心。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;编辑/秘书&lt;/strong&gt;：外科医生口述或手写，编辑负责把文档变成清晰、统一、可发布的正式文档。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;程序职员&lt;/strong&gt;：维护所有代码版本、运行记录、测试数据——机器时间时代的&amp;quot;版本与构建管家&amp;rdquo;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;工具师&lt;/strong&gt;：为团队打造专用工具、脚本、测试设施。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;测试员&lt;/strong&gt;：根据规格写测试用例，并且&lt;strong&gt;从规格里找矛盾&lt;/strong&gt;——他是架构师的第一批批评者。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;语言专家&lt;/strong&gt;：精通语言犄角旮旯的顾问，帮团队写出既优雅又高效的代码。&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    S[&amp;#34;外科医生 首席程序员&amp;lt;br/&amp;gt;设计 + 核心代码 + 负全责&amp;#34;]
    C[&amp;#34;副手&amp;lt;br/&amp;gt;讨论对象与备份&amp;#34;]
    AD[&amp;#34;管理员&amp;lt;br/&amp;gt;钱、人、设备&amp;#34;]
    ED[&amp;#34;编辑&amp;lt;br/&amp;gt;文档产出与统一&amp;#34;]
    CL[&amp;#34;程序职员&amp;lt;br/&amp;gt;版本与运行记录&amp;#34;]
    TL[&amp;#34;工具师&amp;lt;br/&amp;gt;内部工具与设施&amp;#34;]
    TE[&amp;#34;测试员&amp;lt;br/&amp;gt;用例设计与规格挑刺&amp;#34;]
    LX[&amp;#34;语言专家&amp;lt;br/&amp;gt;语言技艺顾问&amp;#34;]
    C --&amp;gt; S
    AD --&amp;gt; S
    ED --&amp;gt; S
    CL --&amp;gt; S
    TL --&amp;gt; S
    TE --&amp;gt; S
    LX --&amp;gt; S
    style S fill:#fff3cd&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;注意这个结构的颠覆点：&lt;strong&gt;其他人不是外科医生的下属执行者，而是他的支撑系统。&lt;/strong&gt; 今天回头看，这是&amp;quot;10x 工程师团队&amp;quot;最早的严肃版本——结论不是&amp;quot;找 10 个 10x&amp;quot;，而是&amp;quot;&lt;strong&gt;让那个 10x 的人 100% 的时间都在做只有他能做的事&lt;/strong&gt;&amp;quot;。大项目怎么办？分成若干段，每段一个外科手术队伍，段与段之间用架构对齐——又回到概念完整性。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;五第二系统效应你最危险的作品是第二个&#34;&gt;五、第二系统效应：你最危险的作品是第二个&lt;/h2&gt;
&lt;p&gt;全书最精妙的心理学洞察之一：&lt;strong&gt;第二系统效应（second-system effect）&lt;/strong&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;一个人在设计他的第二个系统时，会把它做成最危险的系统——因为他会把第一个系统里没用上的所有想法，全都塞进去。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;机制是这样的：设计第一个系统时，人是谨慎的、不自信的，处处收敛；那些&amp;quot;如果再来一次我一定要加&amp;quot;的念头，被一条条记在心里的清单上。设计第二个系统时，自信来了，约束松了，那张清单被整体倒出来——加上第一版遗留功能的修饰欲，结果是&lt;strong&gt;功能膨胀、概念混杂、复杂度失控&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;Brooks 的自白是：OS/360 就是大量设计者的&amp;quot;第二系统&amp;quot;（多数人的第一系统是 1410、7090 系列），所以它的链接器、它的各种设施，都堆满了&amp;quot;这次终于可以做了&amp;quot;的东西。而他给架构师的建议只有一句：&lt;strong&gt;为每个功能问一句&amp;quot;它值它占用的那份复杂度预算吗&amp;quot;&lt;/strong&gt;；并且始终意识到自己在设计第几个系统——第三个系统的设计师，往往才第一次有真正的克制能力。&lt;/p&gt;
&lt;p&gt;工程映射：每个&amp;quot;重写 2.0&amp;quot;项目都是第二系统效应的高危现场。旧系统的痛点清单天然会被翻译成新系统的功能清单，而旧系统&amp;quot;为什么不支持 X&amp;quot;的答案（往往是成本，不是疏忽）在重写时被遗忘。&lt;strong&gt;重写立项评审时，把&amp;quot;砍掉的功能清单&amp;quot;和&amp;quot;新增的功能清单&amp;quot;摆在一起看，能救命。&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;六计划抛弃原型第一个版本注定要扔&#34;&gt;六、计划抛弃原型：第一个版本注定要扔&lt;/h2&gt;
&lt;p&gt;原话极不客气：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Plan to throw one away; you will, anyhow.（计划抛弃一个吧；反正你一定会抛弃的。）&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;意思是：你做的第一个版本，无论多用心，都是&amp;quot;试验品&amp;quot;——只有做完它，你才真正理解需求、理解约束、理解用户真正要什么。管理层如果不&amp;quot;计划&amp;quot;抛弃它，它就会被当作产品硬撑下去，背着一个为学习而生的架构进入维护地狱。&lt;/p&gt;
&lt;p&gt;这句话常被误读为&amp;quot;随便写一版就行&amp;quot;。Brooks 的本意严格得多：&lt;strong&gt;抛弃是计划内的，所以要为&amp;quot;抛弃&amp;quot;设计&lt;/strong&gt;——原型和生产系统用不同的人力配比、不同的质量预期、不同的心态；原型的使命是最大化学习，不是最大化寿命。后来的演进还有一条：&lt;strong&gt;唯一比&amp;quot;计划抛弃&amp;quot;更贵的，是假装不用抛弃。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这条思想后来开枝散叶：快速原型、MVP、以及 Brooks 自己在《没有银弹》里提出的&lt;strong&gt;增量式构建（grow, not build）&lt;/strong&gt;——软件不该是&amp;quot;建造&amp;quot;出来的，该是&amp;quot;生长&amp;quot;出来的：先让一个能跑的最小骨架活着，然后每加一个功能都保持系统可运行。1995 年纪念版里他承认，当年&amp;quot;抛弃一个&amp;quot;的说法被他自己修正为更平滑的增量演化，但内核没变：&lt;strong&gt;不要指望第一枪就命中，把迭代当成常态写进计划。&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;七文档与工作手册被遗忘的主角&#34;&gt;七、文档与工作手册：被遗忘的主角&lt;/h2&gt;
&lt;p&gt;这本书有个今天读来格外超前的观点：&lt;strong&gt;项目经理最重要的日常工作是文档，不是代码。&lt;/strong&gt; Brooks 花了整整一章讲「项目工作手册」（project workbook）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;它是什么&lt;/strong&gt;：项目所有正式文档的集合——目标、规格、进度、接口定义、会议纪要、变更记录。它不是档案柜，是项目的&lt;strong&gt;活的中枢&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;为什么必须有&lt;/strong&gt;：第一，写作强制清晰——&amp;ldquo;写在纸上的决策，模糊性会自己暴露&amp;rdquo;，很多争吵在落成文字那一刻就解决了；第二，文档是沟通结构的载体，几百人的项目里，工作手册是唯一能让&amp;quot;每个人看到同一本事实&amp;quot;的装置。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;怎么维护&lt;/strong&gt;：所有变更都要进手册，手册必须人人可得、持续更新（OS/360 时代用缩微胶片分发，他特意讨论了每日更新机制——放今天就是 wiki/文档站）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;配套还有一条 OS/360 的组织创新：&lt;strong&gt;让文档成为规格的唯一权威形态&lt;/strong&gt;。架构师写正式的外部规格说明，实现中出现歧义时，由架构师裁决并&lt;strong&gt;把裁决写回规格&lt;/strong&gt;——规格永远是最新事实，不是&amp;quot;最初的美好愿望&amp;quot;。&lt;/p&gt;
&lt;p&gt;今天的映射一目了然：ADR（架构决策记录）、design doc 评审、&amp;ldquo;接口以文档为准而非以聊天为准&amp;rdquo;，全是工作手册的现代转世。Brooks 在 1975 年就说清楚了我们 2020 年代还在学的事：&lt;strong&gt;异步的、成文的沟通，是大团队唯一可扩展的沟通。&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;八没有银弹本质复杂度与偶然复杂度&#34;&gt;八、没有银弹：本质复杂度与偶然复杂度&lt;/h2&gt;
&lt;p&gt;1986 年，Brooks 发表了那篇比书本身更著名的论文：「没有银弹——软件工程中本质与偶然的问题」（No Silver Bullet: Essence and Accidents of Software Engineering），1995 年纪念版收入书中。&lt;/p&gt;
&lt;p&gt;核心论断：&lt;strong&gt;没有任何一项技术或管理进步，能在十年内让软件生产率提高一个数量级。&lt;/strong&gt; 因为软件的困难要分两类：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;本质复杂度（essential）&lt;/strong&gt;：问题域本身固有的复杂度——概念结构必须精确、一致、完整；软件是&amp;quot;纯粹的思想制品&amp;quot;，它的复杂度不会因为换工具而消失。就像银弹能杀狼人，但软件工程里没有狼人——没有一个叫&amp;quot;技术&amp;quot;的妖怪一枪打死就天下太平。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;偶然复杂度（accidental）&lt;/strong&gt;：我们制造过程附带的复杂度——机器慢、语言啰嗦、工具难用、环境配置地狱。&lt;strong&gt;这部分是可以消灭的&lt;/strong&gt;，而且历史上已经消灭了大部分：高级语言干掉了汇编的偶然复杂度，分时系统干掉了批处理的排队等待，统一硬件平台干掉了移植地狱。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Brooks 的关键预测逻辑是：到 1986 年，偶然复杂度已经被压缩到不是主要矛盾了——剩下的硬骨头几乎全是本质复杂度，而&lt;strong&gt;本质复杂度不响应任何&amp;quot;银弹&amp;quot;&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;那怎么办？论文给出了四条&amp;quot;有希望的铜弹&amp;quot;，条条在后来兑现：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;买，别造&lt;/strong&gt;（buy vs. build）：能买现成软件就别自己写——自己写的那部分才承载本质复杂度。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;需求精炼与快速原型&lt;/strong&gt;：最难的本质问题是&amp;quot;搞清楚到底要造什么&amp;quot;，用原型和用户一起把需求逼出来。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;增量式生长&lt;/strong&gt;（grow, not build）：系统永远保持可运行，小步叠加。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;培养伟大的设计师&lt;/strong&gt;：软件生产率的最大方差来自人——顶尖设计师和普通设计师的差距是数量级的，把培养设计师当成组织的核心投资。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;1986 年之后的四十年基本在给这篇论文做注脚：面向对象、组件化、开源生态、云、低代码，每一波都消灭了新的偶然复杂度，每一波之后项目照样的难——因为难的那部分从来不在工具里，在&amp;quot;我们要造的东西本身&amp;quot;里。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;九20-周年纪念版brooks-的自我检讨&#34;&gt;九、20 周年纪念版：Brooks 的自我检讨&lt;/h2&gt;
&lt;p&gt;1995 年纪念版最珍贵的部分，是 Brooks 回头看自己二十年前的论断，一条条打分：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;仍然坚信的&lt;/strong&gt;：概念完整性是系统最重要的品质；人月不可互换；加人让延期项目更晚；工作手册必不可少；第二系统效应真实存在。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;修正的&lt;/strong&gt;：「计划抛弃一个」太粗暴，更准确的表述是增量演化（grow）；结构化编程、面向对象属于消灭偶然复杂度，不是银弹——这点他赌对了。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;认错的&lt;/strong&gt;：他当年推崇&amp;quot;信息隐藏到团队之间也互相保密&amp;quot;的做法，后来承认过度了——Parnas 的信息隐藏原则适用于模块接口，不该用来隔离团队之间的知识。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;被时代证明最对的&lt;/strong&gt;：对&amp;quot;伟大设计师&amp;quot;的押注。20 年后他更确信：软件质量的天花板由少数顶尖设计者决定，组织的头等大事是发现他们、培养他们、别用流程磨平他们。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这种&amp;quot;作者亲自给旧书做 code review&amp;quot;的结构，本身就是对全书精神的践行：&lt;strong&gt;把二十年前的决策放到今天的光下重新评审，而不是假装当年的自己永远正确。&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;十落地实践我自己在用的四条&#34;&gt;十、落地实践：我自己在用的四条&lt;/h2&gt;
&lt;p&gt;这本书不是读完就完的，我把其中几条固化成了自己带项目时的检查动作：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;报排期时先分类任务&lt;/strong&gt;。任何估算之前，先问&amp;quot;这块工作能不能无沟通地并行拆开&amp;quot;。拆不动的部分（联调、设计、疑难 bug），单独列出来按顺序排，并且明确告诉需求方：这部分加人不解决问题，加人只会更慢。把 Brooks 定律提前说出来，比延期之后再解释有效得多。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;里程碑只写&amp;quot;可验证的事&amp;quot;&lt;/strong&gt;。排期表里禁止出现&amp;quot;开发完成 80%&amp;ldquo;这类状态词，全部换成&amp;quot;接口联调通过&amp;quot;&amp;ldquo;性能压测达标&amp;quot;这种第三方可验证的硬节点。项目是一天一天延期的，锋利的里程碑是让&amp;quot;每天的小落后&amp;quot;无处藏身。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;重写项目先审&amp;quot;砍掉清单&amp;rdquo;&lt;/strong&gt;。任何重构、2.0 立项，评审材料里必须有两栏：新系统不做什么、为什么不做。没有砍掉清单的重写方案，默认是第二系统效应发作，打回。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;护住团队里的&amp;quot;外科医生&amp;rdquo;&lt;/strong&gt;。识别出那个承担核心设计的人，然后系统性地替他挡事：杂会替他推掉、文档有人帮他整理、工具不顺有人帮他修。他的每一分钟专注时间，就是整个团队的产能上限。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;四条背后其实是同一个原则，也是这本书教我的最重要的一件事：&lt;strong&gt;软件工程里大多数&amp;quot;管理动作&amp;quot;的反面才是对的——加人不如减人，催进度不如砍范围，民主设计不如贵族专制，憋大招不如小步生长。&lt;/strong&gt; Brooks 在五十年前就把这些&amp;quot;反直觉&amp;quot;写成了定理，我们要做的只是别再交一遍学费。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;十一带走的清单&#34;&gt;十一、带走的清单&lt;/h2&gt;
&lt;p&gt;如果只能记住几件事，记这些：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;人月不可互换&lt;/strong&gt;。报工期前先问：这个任务属于&amp;quot;收麦子型&amp;quot;还是&amp;quot;生孩子型&amp;quot;？后者加人无用。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Brooks 定律&lt;/strong&gt;：延期的项目加人只会更晚。赶工的正确动作是砍范围、保核心，不是借调支援。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;里程碑要锋利&lt;/strong&gt;：&amp;ldquo;完成 90%&amp;ldquo;不是进度，是幻觉；只有&amp;quot;可验证、不可含糊&amp;quot;的节点才是进度。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;概念完整性 ＞ 功能数量&lt;/strong&gt;。架构出自一人或极少数人；架构师管&amp;quot;系统是什么样&amp;rdquo;，实现者管&amp;quot;怎么造出来&amp;rdquo;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;警惕第二系统效应&lt;/strong&gt;：重写立项时，先审&amp;quot;砍掉清单&amp;quot;，再审&amp;quot;新增清单&amp;quot;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;计划抛弃原型&lt;/strong&gt;：第一版的使命是学习；要么计划抛弃它，要么被它拖死。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;文档是项目的中枢&lt;/strong&gt;：写下来，模糊会自己暴露；规格是唯一权威，裁决要写回规格。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;没有银弹&lt;/strong&gt;：工具的进步只消灭偶然复杂度；本质复杂度只能靠更好的需求（原型）、更好的生长方式（增量）、更好的人（设计师）来对付。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;投资顶尖设计师&lt;/strong&gt;：人员方差是软件生产率里最大的方差，这件事 1975 年成立，今天更成立。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;参考资料&#34;&gt;参考资料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Frederick P. Brooks Jr., &lt;em&gt;The Mythical Man-Month: Essays on Software Engineering&lt;/em&gt;, Anniversary Edition, Addison-Wesley, 1995（含《没有银弹》全文及作者 20 年回顾）。&lt;/li&gt;
&lt;li&gt;Frederick P. Brooks Jr., &amp;ldquo;No Silver Bullet: Essence and Accidents of Software Engineering&amp;rdquo;, &lt;em&gt;Computer&lt;/em&gt;, 1987（1986 年原始论文）。&lt;/li&gt;
&lt;li&gt;Frederick P. Brooks Jr., &lt;em&gt;The Design of Design&lt;/em&gt;, Addison-Wesley, 2010（Brooks 晚年对&amp;quot;设计&amp;quot;本身的系统思考，可视作本书续篇）。&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    <item>
      <title>《凤凰项目》读书笔记：用一本小说搞懂 DevOps 的三步工作法</title>
      <link>https://ftmao.com/blog/bookstore/phoenix-project-book-notes/</link>
      <pubDate>Tue, 08 Aug 2023 10:40:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/bookstore/phoenix-project-book-notes/</guid>
      <description>&lt;h1 id=&#34;凤凰项目读书笔记用一本小说搞懂-devops-的三步工作法&#34;&gt;《凤凰项目》读书笔记：用一本小说搞懂 DevOps 的三步工作法&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;目标读者：写代码的、搞运维的、带团队的，尤其是觉得&amp;quot;我们部门天天救火、需求永远延期、发布必出事故&amp;quot;的人。&lt;br&gt;
参考书籍：Gene Kim, Kevin Behr, George Spafford《凤凰项目：一个 IT 运维的传奇故事》（The Phoenix Project）&lt;br&gt;
阅读目标：不用啃方法论大部头，通过一个故事建立 DevOps 的完整直觉——为什么 IT 问题本质上是&amp;quot;工厂管理问题&amp;quot;，以及三步工作法到底怎么用。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;0-写在前面为什么是小说体的-devops-入门&#34;&gt;0. 写在前面：为什么是&amp;quot;小说体&amp;quot;的 DevOps 入门&lt;/h2&gt;
&lt;p&gt;市面上讲 DevOps 的书大多是清单式的：CI/CD、IaC、监控、文化……知识点都对，但读完你还是不知道&lt;strong&gt;从哪一步开始改&lt;/strong&gt;，更不知道怎么跟老板解释&amp;quot;为什么要改&amp;quot;。&lt;/p&gt;
&lt;p&gt;《凤凰项目》的作者显然也意识到了这一点，所以他们写了一本小说。这本书的思路来自一个更老的先例：高德拉特（Eliyahu Goldratt）1984 年的《目标》（The Goal）——一本用工厂经理拯救濒临倒闭工厂的故事来讲&lt;strong&gt;约束理论&lt;/strong&gt;（TOC, Theory of Constraints）的小说。《凤凰项目》就是把《目标》的工厂换成了一家公司的 IT 部门，把生产线换成了&amp;quot;需求 → 开发 → 测试 → 部署 → 运维&amp;quot;这条价值流。&lt;/p&gt;
&lt;p&gt;一句话理解全书：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;IT 部门就是一座工厂，代码从流水线的这头进去、从部署那头出来。工厂的毛病 IT 全有：在制品堆积、瓶颈工位、紧急插单、返工报废。所以工厂管理那套成熟的方法（约束理论、精益生产、丰田生产方式），搬到 IT 里就是 DevOps。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;下面我们按&amp;quot;剧情主线 → 核心概念 → 现实对照&amp;quot;的顺序展开。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;1-剧情主线一个烂摊子和一个临危受命的运维-vp&#34;&gt;1. 剧情主线：一个烂摊子和一个临危受命的运维 VP&lt;/h2&gt;
&lt;p&gt;故事发生在一家虚构的汽车零部件制造公司 &lt;strong&gt;Parts Unlimited（零件公司）&lt;/strong&gt;。公司正在被竞争对手用互联网打法吊打，董事会给管理层下了死命令：两年内把公司救回来，否则 IT 部门整体外包。&lt;/p&gt;
&lt;p&gt;救亡图存的希望寄托在一个叫 &lt;strong&gt;&amp;ldquo;凤凰项目&amp;rdquo;（Project Phoenix）&lt;/strong&gt; 的大项目上——一个面向电商化的核心系统改造，号称要让公司重新获得和对手正面竞争的能力。问题是：这个项目已经严重超支、延期数年，功能越堆越多，离上线遥遥无期。&lt;/p&gt;
&lt;p&gt;开篇第一天，主角 &lt;strong&gt;Bill Palmer（比尔·帕尔默）&lt;/strong&gt; 还是个小运维经理，正在处理公司工资系统（Payroll）宕机的大事故——全公司发不出工资。就在这一天，两位高管（CIO 和运维 VP）先后被炒，CEO &lt;strong&gt;Steve Masters&lt;/strong&gt; 直接把 Bill 拎上来：从今天起，你就是 IT 运维副总裁。&lt;/p&gt;
&lt;p&gt;Bill 接手的局面堪称教科书级的灾难现场：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;凤凰项目&lt;/strong&gt;像吞金兽，吃掉全公司最优秀的工程师，却连一次完整的测试部署都做不起来；&lt;/li&gt;
&lt;li&gt;业务部门绕过 IT 自己买 SaaS、插紧急需求，IT 完全不知道有多少工作在流动；&lt;/li&gt;
&lt;li&gt;所有关键知识都集中在一个叫 &lt;strong&gt;Brent&lt;/strong&gt; 的超级工程师脑子里，任何系统的任何故障最后都得找他；&lt;/li&gt;
&lt;li&gt;每次发布都是通宵加班 + 回滚 + 更多通宵加班的恶性循环；&lt;/li&gt;
&lt;li&gt;安全审计部天天发整改单，没人理，直到差点被监管和董事会抓住把柄。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;书里还有一个关键人物：&lt;strong&gt;Erik&lt;/strong&gt;，一个董事会的准董事候选人，平时蹲在离公司总部最近的一个制造工厂里，看起来像退休技术宅，实际上是整个故事的&amp;quot;导师&amp;quot;角色。他每次出场都把 Bill 拉到工厂里参观，讲一些看似答非所问的工厂管理道理——&lt;strong&gt;Erik 就是高德拉特在书里的化身&lt;/strong&gt;，他说的每句话都是约束理论。&lt;/p&gt;
&lt;p&gt;剧情的高潮和转折大致是：一次为了赶董事会死线的&lt;strong&gt;灾难性发布&lt;/strong&gt;（凤凰被强行上线，引发支付数据泄露级别的混乱）之后，Steve 祭出狠招——&lt;strong&gt;冻结一切发布&lt;/strong&gt;。在被迫停下来的这段时间里，Bill 团队才真正看清自己的工厂长什么样：堆积如山的在制品、四处流动的紧急变更、以及那个被所有人依赖的 Brent。于是他们开始一步步疏解瓶颈、限制在制品、建立反馈回路，最后孵化出代号 &lt;strong&gt;&amp;ldquo;独角兽&amp;rdquo;（Unicorn）&lt;/strong&gt; 的独立小团队，用持续部署的方式交付电商功能，让公司起死回生。&lt;/p&gt;
&lt;p&gt;Bill 本人在故事结尾升任 CIO——这个安排也是作者的态度：&lt;strong&gt;DevOps 不是某个团队的局部优化，而是 IT 一把手工程。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;主要人物和概念可以对照着记：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;mindmap
  root((&amp;#34;凤凰项目&amp;#34;))
    人物
      Bill Palmer：运维 VP→CIO（主角视角）
      Steve Masters：CEO，冻结发布的拍板人
      Erik：导师角色，Goldratt 的化身
      Brent：超级工程师，全公司的瓶颈
      Wes/Patty：运维总监/变更管理负责人
    核心概念
      四种工作类型
      三步工作法
      约束理论 TOC
      精益与丰田生产方式
    关键事件
      工资系统宕机（开篇）
      凤凰灾难性发布（谷底）
      冻结发布（转折）
      独角兽项目（翻身）&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;11-根因开发与运维的对立目标&#34;&gt;1.1 根因：开发与运维的&amp;quot;对立目标&amp;quot;&lt;/h3&gt;
&lt;p&gt;书里把 IT 一切混乱的总根源归结为一个结构性矛盾，值得单独拎出来：&lt;strong&gt;开发和运维被两套相反的目标函数考核。&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;开发的 KPI 是&amp;quot;快&amp;quot;：更多功能、更快上线、响应业务变化；&lt;/li&gt;
&lt;li&gt;运维的 KPI 是&amp;quot;稳&amp;quot;：可用性、不出事故、别动我的生产环境。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;于是开发把代码&amp;quot;扔过墙&amp;quot;就算交差，运维为了自保层层设卡（审批、冻结窗口、几十页的变更单），结果是部署越来越难、间隔越来越长、每次部署的批量越来越大——&lt;strong&gt;批量越大风险越高，风险越高越想少部署&lt;/strong&gt;，形成一个不断恶化的&amp;quot;向下的螺旋&amp;quot;。书里的凤凰项目几年憋一发，就是这条螺旋的终点。三步工作法的本质，就是把两套目标函数统一成&amp;quot;&lt;strong&gt;安全地、频繁地把价值送到用户手里&lt;/strong&gt;&amp;ldquo;这一件事——开发对生产负责，运维为交付提速。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;2-四种工作类型先看清你的工厂里在流什么&#34;&gt;2. 四种工作类型：先看清你的工厂里在流什么&lt;/h2&gt;
&lt;p&gt;Erik 给 Bill 上的第一课是：&lt;strong&gt;在解决任何问题之前，先搞清楚工厂里到底有几种工作在流动。&lt;/strong&gt; IT 里的工作只有四种：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;业务项目（Business Projects）&lt;/strong&gt;&lt;br&gt;
业务部门要的功能：凤凰项目、新的促销系统、给销售做的报表。这类工作有明确的外部客户，是&amp;quot;公司存在的原因&amp;rdquo;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;IT 内部项目（IT Internal Projects）&lt;/strong&gt;&lt;br&gt;
IT 自己发起的工作：服务器升级、数据库迁移、安全补丁、技术债偿还。它们不产生直接收入，但没有它们，业务项目迟早死在烂地基上。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;变更（Changes）&lt;/strong&gt;&lt;br&gt;
上面两类项目落地时产生的具体操作：改配置、发版本、加字段。书里的关键洞察是——&lt;strong&gt;大多数事故由变更引起，而变更的流程往往既没人看见、也没人管理&lt;/strong&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;计划外工作（Unplanned Work）&lt;/strong&gt;&lt;br&gt;
救火、故障恢复、临时支援、&amp;ldquo;帮我看一眼这个报错&amp;rdquo;。它的特点是：&lt;strong&gt;不请自来、优先级永远最高、而且会把前三类工作的时间偷走。&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;一句话理解：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;计划外工作是四种工作里唯一的&amp;quot;负资产&amp;quot;——它不光消耗资源，还打断所有正在进行的工作，制造新的错误，从而制造更多计划外工作。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;书里有个很扎心的细节：Bill 让团队统计自己的工作去向，发现几乎没有人在做&amp;quot;计划内的事&amp;quot;——每个人都在被随机的紧急请求打断。这在工厂里叫&amp;quot;紧急插单&amp;quot;，在 IT 里我们叫&amp;quot;@所有人 快帮忙看下&amp;quot;。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    subgraph 流入
        A[&amp;#34;业务项目&amp;lt;br/&amp;gt;（赚钱的功能）&amp;#34;] --&amp;gt; WIP
        B[&amp;#34;IT 内部项目&amp;lt;br/&amp;gt;（打地基）&amp;#34;] --&amp;gt; WIP
        C[&amp;#34;变更&amp;lt;br/&amp;gt;（落地动作）&amp;#34;] --&amp;gt; WIP
        D[&amp;#34;计划外工作&amp;lt;br/&amp;gt;（救火·打断一切）&amp;#34;] -.-&amp;gt;|&amp;#34;插队抢占&amp;#34;| WIP
    end

    WIP[&amp;#34;在制品队列&amp;lt;br/&amp;gt;WIP 越堆越高&amp;lt;br/&amp;gt;周期时间越来越长&amp;#34;]

    WIP --&amp;gt; E[&amp;#34;部署上线&amp;lt;br/&amp;gt;产生业务价值&amp;#34;]
    WIP -.-&amp;gt;|&amp;#34;缺陷与事故&amp;#34;| D

    style D fill:#ffcccc
    style WIP fill:#fff3cd&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;注意图里那条反向虚线：&lt;strong&gt;计划外工作不但插队，还会生出新的事故&lt;/strong&gt;——急匆匆的救火往往是又一个 bug 的种子。这就是&amp;quot;越忙越乱&amp;quot;的数学本质。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;小数字例子&lt;/strong&gt;：假设一个工程师每天 8 小时，原本排满 2 个任务。每被插入 1 次救火，打断成本大约是&amp;quot;上下文切换 + 排查 + 修复 + 切回来&amp;quot;——书里借 Erik 之口引用了一个经典结论：一个人同时挂在手上的事越多，每件事完成的周期时间就按非线性膨胀。这不是态度问题，是排队论（利特尔法则：周期时间 = 在制品数量 ÷ 吞吐率）。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;3-约束理论找到你的-brent&#34;&gt;3. 约束理论：找到你的 Brent&lt;/h2&gt;
&lt;h3 id=&#34;31-一句话理解-toc&#34;&gt;3.1 一句话理解 TOC&lt;/h3&gt;
&lt;p&gt;约束理论（TOC）来自高德拉特的《目标》，核心一句话：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;任何系统的整体产能，只由系统中最慢的那个环节（瓶颈/约束）决定。优化瓶颈之外的任何环节，都是在浪费钱，甚至帮倒忙。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;工厂例子很直观：一条流水线 5 个工位，第 3 个工位每小时只能加工 10 个零件，其余工位都能做 50 个。那你给工位 1 提速到每小时 100 个有意义吗？没有——零件只会在工位 3 前面堆成山，&lt;strong&gt;在制品增加，但出货一件都不会多&lt;/strong&gt;。工厂里的真金白银只发生在&amp;quot;零件卖出厂门&amp;quot;那一刻；IT 里对应的就是&amp;quot;功能部署到生产环境并被用户使用&amp;quot;那一刻。没上线的代码不是资产，是库存，而且是会腐烂的库存（需求变了、上下文忘了、合并冲突了）。&lt;/p&gt;
&lt;h3 id=&#34;32-brent那个什么都懂的人就是瓶颈&#34;&gt;3.2 Brent：那个什么都懂的人，就是瓶颈&lt;/h3&gt;
&lt;p&gt;《凤凰项目》里最精妙的设计就是 Brent 这个角色。Brent 是 Parts Unlimited 的传奇工程师：他知道每个系统埋在哪个角落、每条配置为什么长那样、每次故障的历史前因。于是公司里形成了一个铁律——&lt;strong&gt;重要的事情都得 Brent 参与&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;凤凰项目要部署？只有 Brent 懂环境配置，找他。&lt;/li&gt;
&lt;li&gt;工资系统挂了？只有 Brent 知道那段祖传代码，找他。&lt;/li&gt;
&lt;li&gt;网络要改造？只有 Brent 记得十年前为什么这么布线，找他。&lt;/li&gt;
&lt;li&gt;甚至业务部门自己想买软件，也要&amp;quot;Brent 看一下能不能集成&amp;quot;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;结果 Brent 的工作队列永远排在几个月之后，而&lt;strong&gt;所有关键路径都从 Brent 的工位前经过&lt;/strong&gt;——他就是整条流水线的瓶颈工位。更糟的是，因为 Brent 太能干，大家救火也找他，计划外工作进一步抢占他的时间，瓶颈被救火放大。&lt;/p&gt;
&lt;p&gt;识别瓶颈的方法书里也给了，非常朴素：&lt;strong&gt;看谁面前的队列最长，看谁的名字出现在所有任务的依赖列表里，看谁永远约不到会议时间。&lt;/strong&gt; 工厂里的瓶颈工位有个特征——它前面永远堆着最多的待加工零件。Brent 前面堆的，是全公司的工单。&lt;/p&gt;
&lt;h3 id=&#34;33-疏解瓶颈toc-五步法&#34;&gt;3.3 疏解瓶颈：TOC 五步法&lt;/h3&gt;
&lt;p&gt;约束理论的改进套路是五个步骤，书里完整走了一遍：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;识别约束&lt;/strong&gt;：确认 Brent（以及少数关键岗位）是系统瓶颈，而不是去怪&amp;quot;团队不努力&amp;quot;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;挖尽约束&lt;/strong&gt;：瓶颈的每一分钟都不能浪费。让 Brent 的时间只花在&amp;quot;只有 Brent 能做的事&amp;quot;上——别再让他回工单、开无关的会、修不需要他的机器。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;迁就约束&lt;/strong&gt;：整个系统的节奏向瓶颈看齐。在 Brent 消化完之前，不许再往上堆新工作（这在工厂里叫&amp;quot;绳子&amp;quot;机制，在 IT 里就是 WIP 限制）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;打破约束&lt;/strong&gt;：给约束扩容。书里的做法是让 Brent 的工作&lt;strong&gt;文档化、标准化&lt;/strong&gt;：每次 Brent 修东西，旁边必须有第二个人观摩记录，把他的隐性知识变成显性 runbook，逐步培养出&amp;quot;第二个、第三个 Brent&amp;quot;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;回到第一步&lt;/strong&gt;：约束疏解后，瓶颈会转移到别处（比如自动化测试、比如部署流程），继续循环。&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    subgraph 疏解前
        direction TB
        A1[&amp;#34;所有需求&amp;#34;] --&amp;gt; B1[&amp;#34;Brent&amp;lt;br/&amp;gt;队列无限长&amp;lt;br/&amp;gt;周期时间失控&amp;#34;]
        B1 --&amp;gt; C1[&amp;#34;上线&amp;#34;]
        D1[&amp;#34;救火请求&amp;#34;] -.-&amp;gt;|&amp;#34;随时打断&amp;#34;| B1
    end

    subgraph 疏解后
        direction TB
        A2[&amp;#34;需求按优先级进入&amp;#34;] --&amp;gt; W2[&amp;#34;WIP 限制&amp;lt;br/&amp;gt;排队可见&amp;#34;]
        W2 --&amp;gt; B2[&amp;#34;Brent 只做&amp;lt;br/&amp;gt;只有他能做的事&amp;#34;]
        W2 --&amp;gt; B3[&amp;#34;文档化 + 带教&amp;lt;br/&amp;gt;复制出更多 Brent&amp;#34;]
        B2 --&amp;gt; C2[&amp;#34;上线&amp;#34;]
        B3 --&amp;gt; C2
        D2[&amp;#34;救火请求&amp;#34;] --&amp;gt; W2
    end&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;这一条是全书对工程管理最有杀伤力的观点，值得单独抄一遍：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&amp;ldquo;把最强的工程师从所有救火中撤出来&amp;quot;听起来反直觉，但恰恰是让系统恢复流动的唯一办法。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;现实映射也很常见：几乎每个团队都有一个&amp;quot;Brent&amp;rdquo;——核心系统的唯一懂行人、所有 MR 的指定 reviewer、线上问题的最终兜底人。这个角色的存在本身就是风险（业界叫 bus factor = 1），而管理他的正确姿势不是&amp;quot;多招几个一样强的人&amp;quot;（招不到），而是&lt;strong&gt;把他的知识变成流程和文档，让他只干真正不可复制的那部分&lt;/strong&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;4-三步工作法devops-的全部内容&#34;&gt;4. 三步工作法：DevOps 的全部内容&lt;/h2&gt;
&lt;p&gt;三步工作法（The Three Ways）是《凤凰项目》的骨架，也是后来《DevOps Handbook》的理论内核。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    subgraph S1[&amp;#34;第一步：流动原则（从左到右）&amp;#34;]
        direction LR
        Dev[&amp;#34;开发&amp;#34;] --&amp;gt; Test[&amp;#34;测试&amp;#34;] --&amp;gt; Ops[&amp;#34;运维/部署&amp;#34;] --&amp;gt; Cust[&amp;#34;客户价值&amp;#34;]
    end

    subgraph S2[&amp;#34;第二步：反馈原则（从右到左）&amp;#34;]
        direction RL
        Ops2[&amp;#34;运维/生产&amp;#34;] --&amp;gt;|监控/告警/数据| Test2[&amp;#34;测试&amp;#34;]
        Test2 --&amp;gt;|质量信号| Dev2[&amp;#34;开发&amp;#34;]
    end

    subgraph S3[&amp;#34;第三步：持续学习与实验&amp;#34;]
        L[&amp;#34;刻意练习&amp;lt;br/&amp;gt;复盘文化&amp;lt;br/&amp;gt;为改进留时间&amp;#34;]
    end

    S1 &amp;lt;--&amp;gt;|快速、可见、小批量| S2
    S2 --&amp;gt;|知识沉淀为实验| S3&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;41-第一步流动原则flow让工作从左到右永远不要停不要回流&#34;&gt;4.1 第一步：流动原则（Flow）——让工作从左到右，永远不要停、不要回流&lt;/h3&gt;
&lt;p&gt;目标是&lt;strong&gt;缩短从&amp;quot;需求提出&amp;quot;到&amp;quot;价值交付&amp;quot;的周期时间&lt;/strong&gt;。要点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;让工作可见&lt;/strong&gt;：书里第一个改革动作就是建看板（kanban）——把所有正在进行的工作贴在墙上/板上，任何插队需求必须先&amp;quot;有卡&amp;quot;再讨论。看不见的东西无法管理，&amp;ldquo;我才知道你手上还有这个&amp;quot;是大部分混乱的根源。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;限制在制品（WIP）&lt;/strong&gt;：同时开工的事越少，完成得越快。这是利特尔法则的直接推论，也是最容易被管理者忽视的一条——&amp;ldquo;开始新工作&amp;quot;有快感，&amp;ldquo;完成工作&amp;quot;才产生价值。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;小批量&lt;/strong&gt;：大项目（凤凰那种几年憋一发的）是大批量生产的 IT 版，风险随批量指数膨胀。拆小、频繁交付，风险才能线性化。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;减少交接&lt;/strong&gt;：每一次&amp;quot;扔过墙&amp;rdquo;（开发扔给测试、测试扔给运维）都是信息损失和等待。书里凤凰项目最痛的环节就是开发环境、测试环境、生产环境三套配置永远对不上，每次交接都从头踩坑。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;识别并疏解约束&lt;/strong&gt;：即上一节的 TOC 五步法，持续盯住系统里最慢的环节。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;42-第二步反馈原则feedback从右到左越早发现越便宜&#34;&gt;4.2 第二步：反馈原则（Feedback）——从右到左，越早发现、越便宜&lt;/h3&gt;
&lt;p&gt;目标是&lt;strong&gt;让质量信号在生产源头就被制造出来，而不是在用户投诉时才收到&lt;/strong&gt;。要点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;把测试和安全&amp;quot;左移&amp;rdquo;&lt;/strong&gt;：书里 Wes（运维总监）和 Bill 的最大冲突之一，就是安全审计的整改单永远是事后几百页地砸下来。改革方向是让安全要求变成自动化检查，在提交代码的那一刻就反馈，而不是发布前一周开会吵架。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;在生产中看见问题&lt;/strong&gt;：监控、日志、告警是&amp;quot;工厂的质检摄像头&amp;rdquo;。书里 IT 的至暗时刻是&amp;quot;用户先发现支付出错了，我们才知道&amp;quot;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;出问题就停线&lt;/strong&gt;：这条直接来自丰田的&lt;strong&gt;安灯绳（Andon Cord）&lt;/strong&gt;——流水线上任何工人发现缺陷，可以拉绳停掉整条线，全组停下来先修问题。IT 的对应物是&amp;quot;构建红了就没人能提交新代码&amp;quot;、是&amp;quot;出事故先恢复再追责&amp;quot;、是书里后来建立的规则：部署失败自动回滚，而不是边生产边抢修。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;共享的遥测与同理心&lt;/strong&gt;：开发和运维看同一套指标。书里 Bill 和 Wes 关系的转折点，是运维开始理解开发的交付压力、开发开始理解运维的稳定性压力——&lt;strong&gt;目标函数统一了，反馈才有意义&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;43-第三步持续学习与实验continual-learning高风险低信任的反面是习惯成自然的日常&#34;&gt;4.3 第三步：持续学习与实验（Continual Learning）——高风险、低信任的反面是习惯成自然的日常&lt;/h3&gt;
&lt;p&gt;目标是&lt;strong&gt;让组织把&amp;quot;改进&amp;quot;本身当成日常工作的一部分&lt;/strong&gt;。要点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;为改进偿还技术债留预算&lt;/strong&gt;：书里 Bill 争取到的关键资源之一是&amp;quot;把一部分产能固定给内部项目&amp;quot;——否则地基永远没人修，直到塌掉。业界常见的对应做法是预留 20% 左右产能给工程改进（Google SRE 书里有类似的配额思想）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;让实验变便宜&lt;/strong&gt;：能快速试错的前提是&amp;quot;错一次代价小&amp;quot;——小批量、自动回滚、灰度发布、特性开关（feature flag），全是降低实验成本的工具。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;复盘文化&lt;/strong&gt;：事故复盘（postmortem）对事不对人。书里的反面教材是每次事故后管理层的第一反应是&amp;quot;谁干的&amp;quot;，于是人人都学会隐瞒，反馈回路彻底失效——这就是为什么第三步必须建在前两步之上。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;刻意练习故障&lt;/strong&gt;：主动制造故障来训练系统韧性。这条思想后来由 Netflix 的 Chaos Monkey 发扬光大——在工作日随机关生产环境实例，逼所有服务具备自愈能力。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;44-变更管理从审批的墙到自动化的管道&#34;&gt;4.4 变更管理：从&amp;quot;审批的墙&amp;quot;到&amp;quot;自动化的管道&amp;quot;&lt;/h3&gt;
&lt;p&gt;书里 Patty（变更管理负责人）这条线是第二原则的最佳注脚。改革前，Parts Unlimited 的变更流程是典型的&lt;strong&gt;审批驱动&lt;/strong&gt;：一张变更单层层签字、CAB（变更咨询委员会）开会逐条过——看上去严谨，实际上有两个致命问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;审批者根本不了解变更内容&lt;/strong&gt;，签字只是分摊责任，出事了照样出事；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;流程太重，大家就绕开流程&lt;/strong&gt;——大量&amp;quot;紧急变更&amp;quot;和&amp;quot;标准变更&amp;quot;在暗处发生，比没有流程更危险。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;改革方向书里给得很明确：&lt;strong&gt;把人力审批换成自动验证&lt;/strong&gt;。变更信息从工单系统自动采集（谁改的、改了什么、影响哪个系统），风险低的标准变更直接自动放行，高风险的才人工介入；部署本身交给流水线，测试、安全扫描、回滚预案全部自动化。一句话：&lt;strong&gt;变更管理的目的是让正确的变更更快、让危险的变更更难，而不是让所有变更都更慢。&lt;/strong&gt; 这也是现实中 Google 等公司&amp;quot;用错误预算和 SLO 代替 CAB&amp;quot;的逻辑。&lt;/p&gt;
&lt;h3 id=&#34;45-与-sre-的关系&#34;&gt;4.5 与 SRE 的关系&lt;/h3&gt;
&lt;p&gt;如果你读过本博客的 SRE 笔记，会发现三步工作法和 SRE 是同源思想的两套表述：SRE 的&amp;quot;错误预算（error budget）&amp;ldquo;本质上是给第二步的反馈回路装了一个量化阀门——预算花光就冻结发布（这正是书里 Steve 的&amp;quot;冻结发布&amp;rdquo;）；SRE 的&amp;quot;消除琐事（toil）&amp;ldquo;就是限制计划外工作；SRE 的&amp;quot;用工程方法解决运维问题&amp;quot;就是第三步的持续学习。可以把《凤凰项目》理解为&lt;strong&gt;讲&amp;quot;为什么&amp;rdquo;&lt;/strong&gt;，《SRE 书》理解为&lt;strong&gt;讲&amp;quot;怎么做&amp;quot;&lt;/strong&gt;，先读前者再读后者，体感会好很多。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;5-devops-与精益丰田的渊源这套东西不是-it-发明的&#34;&gt;5. DevOps 与精益/丰田的渊源：这套东西不是 IT 发明的&lt;/h2&gt;
&lt;p&gt;《凤凰项目》反复提醒读者：DevOps 的每一个核心概念，在制造业都有原型，而且已经被验证了几十年。&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;DevOps 概念&lt;/th&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;制造业原型&lt;/th&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;作用&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;看板（Kanban）&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;丰田的看板卡&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;让工作可见、控制 WIP&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;持续集成/小批量&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;单件流（One-piece flow）&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;降低批量风险、缩短周期&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;出故障就停构建&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;安灯绳（Andon Cord）&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;质量内建于源头，不让缺陷流向下游&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;价值流映射&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;价值流图（Value Stream Mapping）&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;找出等待与浪费藏在哪里&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;部署自动化&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;快速换模（SMED）&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;把&amp;quot;切换/部署&amp;quot;从小时级压到分钟级&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;复盘与改善&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;Kaizen（持续改善）&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;改进是日常工作，不是运动式整改&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;理解这层渊源有一个很实际的好处：&lt;strong&gt;当你需要向管理层解释 DevOps 时，不用发明新词&lt;/strong&gt;。&amp;ldquo;我们的发布流程就像一条有质检摄像头和停线机制的流水线&amp;quot;比&amp;quot;我们要做 CI/CD 文化建设&amp;quot;有说服力得多。书里 Erik 带 Bill 参观工厂、指着流水线讲 IT 的那些场景，就是在替读者做这层翻译。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;6-关键转折从冻结发布到独角兽&#34;&gt;6. 关键转折：从冻结发布到独角兽&lt;/h2&gt;
&lt;p&gt;书的中后段有几个决定性动作，连起来就是一条完整的&amp;quot;IT 翻身路径&amp;rdquo;，值得单独梳理：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;灾难性发布&lt;/strong&gt;：凤凰项目被董事会死线强推上线，结果支付系统大面积故障、敏感数据风险暴露。这是全书的谷底——&lt;strong&gt;大批量 + 弱反馈 + 瓶颈过载同时引爆&lt;/strong&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;冻结发布（change freeze）&lt;/strong&gt;：Steve Masters 顶着业务压力叫停一切变更。这个动作极其反直觉（&amp;ldquo;业务等不了！&amp;quot;），但它强制系统&amp;quot;停下来看清自己&amp;rdquo;。类比丰田：线停下来不是损失，&lt;strong&gt;带病运转才是损失&lt;/strong&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;盘点在制品与约束&lt;/strong&gt;：冻结期间团队把全部工作摊到看板上，才发现&amp;quot;正在做的事&amp;quot;比大家以为的多一个数量级；同时正式确认 Brent 是核心约束。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;疏解 Brent&lt;/strong&gt;：Brent 的时间被保护起来，每次排障强制带教、强制文档化；不相干的请求被挡在队列外。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;孵化独角兽（Unicorn）&lt;/strong&gt;：把凤凰项目里最急迫的电商功能拆出来，成立一个小而全的独立团队，独立代码库、独立部署流水线，绕开历史包袱，&lt;strong&gt;从&amp;quot;几年一发布&amp;quot;做到&amp;quot;每天多次发布&amp;quot;&lt;/strong&gt;。独角兽不是否定凤凰，而是用实践证明新方式可行，再反向改造凤凰。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;胜利与晋升&lt;/strong&gt;：电商功能帮公司在旺季翻身，Bill 升任 CIO，Steve 升任 COO——DevOps 的胜利最终以&lt;strong&gt;组织形态&lt;/strong&gt;的胜利收场：开发、运维、安全不再是三个互相扔锅的部门，而是围绕价值流协作的一体。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这条路径里最容易被忽略的是第 2 步：&lt;strong&gt;停下来的勇气&lt;/strong&gt;。几乎所有改进失败都死在&amp;quot;边开车边换轮子&amp;quot;——系统过载时，任何改进动作都会变成新的在制品，进一步压低吞吐。先止血，再手术。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;7-现实对照三个真实事件&#34;&gt;7. 现实对照：三个真实事件&lt;/h2&gt;
&lt;p&gt;书中的情节是虚构的，但每一个都有现实的镜像。&lt;/p&gt;
&lt;h3 id=&#34;71-knight-capital45-分钟亏掉-44-亿美元2012反面教材&#34;&gt;7.1 Knight Capital：45 分钟亏掉 4.4 亿美元（2012，反面教材）&lt;/h3&gt;
&lt;p&gt;2012 年 8 月 1 日，美国高频交易公司 Knight Capital 上线新代码。部署过程需要手动在 8 台服务器上更新，结果&lt;strong&gt;只部署了 7 台&lt;/strong&gt;——第 8 台还留着旧代码，而新旧代码复用了同一个标志位，旧代码里一个废弃多年的测试功能（Power Peg）被意外激活。这台服务器开始以每分钟数百万美元的速度下错单，&lt;strong&gt;45 分钟内亏损约 4.4 亿美元&lt;/strong&gt;，公司股价单日崩盘，数月后被收购。&lt;/p&gt;
&lt;p&gt;对照《凤凰项目》，Knight Capital 几乎踩中了书里所有的雷：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;部署是&lt;strong&gt;手工的、不一致的&lt;/strong&gt;（8 台机器人工操作，漏一台没有任何人发现）——违反第一步的自动化与小批量；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;没有自动验证与回滚&lt;/strong&gt;（旧代码带着活弹药上线，出事只能靠人肉拔网线）——违反第二步的反馈；&lt;/li&gt;
&lt;li&gt;变更过程&lt;strong&gt;不可见、无审计&lt;/strong&gt;——变更管理形同虚设。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;书里凤凰的灾难性发布如果拍成纪录片，大概就是这个样子。&lt;/p&gt;
&lt;h3 id=&#34;72-etsy每天几十次部署的软工程正面教材&#34;&gt;7.2 Etsy：每天几十次部署的&amp;quot;软工程&amp;quot;（正面教材）&lt;/h3&gt;
&lt;p&gt;Etsy 是持续部署的早期标杆，其工程师在 2010 年前后公开的实践（后被《持续交付》等书引用）几乎就是&amp;quot;独角兽项目&amp;quot;的现实版：全站每天部署几十次，每次部署只是少量提交；用特性开关把&amp;quot;部署&amp;quot;和&amp;quot;发布&amp;quot;解耦；给发布工程师配一键部署工具和全量监控大屏，新人入职第一天就完成一次真实上线（用小改动建立&amp;quot;部署不可怕&amp;quot;的肌肉记忆）。这就是第一步（流动）+ 第二步（反馈）的教科书落地。&lt;/p&gt;
&lt;h3 id=&#34;73-amazon从单体到面向服务与-two-pizza-team&#34;&gt;7.3 Amazon：从单体到面向服务与 two-pizza team&lt;/h3&gt;
&lt;p&gt;Amazon 在 2000 年代初的增长瓶颈同样是&amp;quot;单体系统 + 部门墙&amp;quot;：代码库互相纠缠、发布互相阻塞。解法是大名鼎鼎的两条——架构上转向&lt;strong&gt;面向服务的架构&lt;/strong&gt;（团队通过稳定 API 交互，可独立部署，这就是后来 AWS 内部文化和 SOA/微服务浪潮的源头之一），组织上推行 &lt;strong&gt;two-pizza team&lt;/strong&gt;（团队小到两个披萨能喂饱，端到端拥有自己的服务：谁构建，谁运行）。这正是书里独角兽团队的原型逻辑：&lt;strong&gt;把约束从&amp;quot;共享的单体&amp;quot;变成&amp;quot;自治的小团队&amp;quot;&lt;/strong&gt;，流动自然恢复。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;8-避坑指南读完书最容易犯的四个错误&#34;&gt;8. 避坑指南：读完书最容易犯的四个错误&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;把 DevOps 理解成买工具&lt;/strong&gt;&lt;br&gt;
Jenkins、Kubernetes、Terraform 都是好工具，但书里从头到尾讲的不是工具，是&lt;strong&gt;工作如何流动&lt;/strong&gt;。先画出自己的价值流、找到自己的 Brent，再谈工具选型。工具解决不了&amp;quot;计划外工作吃掉所有人&amp;quot;的问题。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;把瓶颈理解成&amp;quot;那个最忙的人&amp;quot;，然后让他更忙&lt;/strong&gt;&lt;br&gt;
给 Brent 升职加薪、让他带更多项目，只会把系统卡得更死。正确的动作是保护他的时间、榨出他的知识、复制他的能力。判断标准很简单：&lt;strong&gt;他面前那堆活变少了，全公司的交付反而变快了。&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;不敢停线&lt;/strong&gt;&lt;br&gt;
&amp;ldquo;业务等不了，我们不能冻结发布&amp;quot;是改进最大的拦路虎。但书里和丰田的经验是一致的：系统带病运转时，止血（freeze）不是成本，是投资。当然，冻结要有明确的目标和时间盒——它是手术，不是疗养。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;只要前两步，不要第三步&lt;/strong&gt;&lt;br&gt;
只做流动和反馈，组织会变成一个&amp;quot;高效的救火队&amp;rdquo;：事故发现得快、恢复得快，但同样的坑反复踩。复盘、技术债预算、刻意练习，是把&amp;quot;经验&amp;quot;变成&amp;quot;组织能力&amp;quot;的唯一通道。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id=&#34;9-带走的清单&#34;&gt;9. 带走的清单&lt;/h2&gt;
&lt;p&gt;如果只能记住几件事，记这些：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;IT 是工厂&lt;/strong&gt;：价值只在&amp;quot;部署到生产并被使用&amp;quot;时产生，未上线的工作是库存。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;四种工作&lt;/strong&gt;：业务项目、IT 内部项目、变更、计划外工作。&lt;strong&gt;计划外工作是隐形杀手&lt;/strong&gt;，它插队、打断、还制造更多事故。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;先让工作可见&lt;/strong&gt;（看板），再限制 WIP，周期时间才会下降——利特尔法则是物理，不是态度。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;找到你的 Brent&lt;/strong&gt;：系统吞吐由瓶颈决定，疏解瓶颈（保护时间、文档化、带教复制）的优先级高于一切局部优化。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;三步工作法&lt;/strong&gt;：流动（小批量、少交接、可见化）→ 反馈（测试左移、生产遥测、安灯绳式停线）→ 持续学习（复盘、技术债预算、混沌实验）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DevOps 与 SRE 同源&lt;/strong&gt;：错误预算就是量化的&amp;quot;发布冻结阀门&amp;quot;，消除琐事就是消灭计划外工作。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;现实的学费很贵&lt;/strong&gt;：Knight Capital 45 分钟 4.4 亿美元，买的是&amp;quot;手工部署 + 无回滚&amp;quot;的教训；Etsy 和 Amazon 证明另一条路走得通。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;改进从止血开始&lt;/strong&gt;：必要时冻结发布，停下来看清系统，再动手手术。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;10-延伸阅读&#34;&gt;10. 延伸阅读&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;高德拉特《目标》（The Goal）：《凤凰项目》的思想源头，约束理论的工厂原版故事。&lt;/li&gt;
&lt;li&gt;Gene Kim 等《DevOps Handbook》：《凤凰项目》的方法论展开版，三步工作法逐条落地。&lt;/li&gt;
&lt;li&gt;Google《Site Reliability Engineering》：与本博客 SRE 笔记配合食用，从&amp;quot;为什么&amp;quot;走到&amp;quot;怎么做&amp;quot;。&lt;/li&gt;
&lt;li&gt;《持续交付》（Continuous Delivery, Humble &amp;amp; Farley）：把第一步&amp;quot;流动原则&amp;quot;展开成完整的工程实践。&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    <item>
      <title>《重构》学习总结：改善既有代码的设计，把日课做成习惯</title>
      <link>https://ftmao.com/blog/bookstore/refactoring-fowler-book-notes/</link>
      <pubDate>Tue, 08 Aug 2023 10:40:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/bookstore/refactoring-fowler-book-notes/</guid>
      <description>&lt;h1 id=&#34;重构学习总结改善既有代码的设计把日课做成习惯&#34;&gt;《重构》学习总结：改善既有代码的设计，把日课做成习惯&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;目标读者：写过一两年业务代码、经常被祖传代码折磨、想搞清楚“重构到底怎么落地”的程序员。&lt;br&gt;
参考书籍：Martin Fowler《重构：改善既有代码的设计（第 2 版）》（Refactoring, 2nd Edition）&lt;br&gt;
阅读目标：建立“重构是刷牙不是大扫除”的日常习惯，把坏味道清单变成自己的启发法清单，学会在安全网保护下小步快走。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;0-写在前面重构不是大扫除是刷牙&#34;&gt;0. 写在前面：重构不是大扫除，是刷牙&lt;/h2&gt;
&lt;p&gt;很多人对重构的想象是这样的：找一个安静的下午，拉上需求方开个会，申请两周“专项重构时间”，然后把某个模块推倒重来。这种理解恰恰是 Fowler 最反对的。&lt;/p&gt;
&lt;p&gt;这本书最核心的一个比喻，可以用我自己的话总结为：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;重构不是年底大扫除，是每天刷牙。大扫除轰轰烈烈、一年一次、中间家里一直脏；刷牙小额、频繁、不痛不痒，但牙齿几十年不出大问题。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这个比喻对应到工程上有三个推论：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;小额&lt;/strong&gt;：一次重构就改一点点，编译能过、测试能绿，马上提交。不是攒一个月再交一个巨型 PR。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;频繁&lt;/strong&gt;：每次加功能、改 bug、做 code review 的时候顺手做，融进日常工作流，不需要“立项”。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不改变外在行为&lt;/strong&gt;：这是定义，不是建议。下面马上展开。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;为什么这本书值得每个程序员精读？因为&lt;strong&gt;坏味道清单就是启发法清单的代码版&lt;/strong&gt;。算法书教你遇到问题时用什么启发法求解，Fowler 教你看到代码时用什么启发法判断“这里不对劲”以及“该用哪招”。读熟之后，你看代码的眼光会永久改变——这是少数几本读一遍就能立刻涨功力的书。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;1-一句话理解重构不改变可观察行为的结构调整&#34;&gt;1. 一句话理解重构：不改变可观察行为的结构调整&lt;/h2&gt;
&lt;p&gt;Fowler 给出的定义（第 2 版，第 2 章）：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;重构（名词）：对软件内部结构的一种调整，目的是在不改变软件可观察行为的前提下，提高其可理解性，降低其修改成本。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;拆开看有三个关键词：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;结构调整&lt;/strong&gt;：动的是代码的组织方式——函数怎么拆、类怎么分、数据放哪里。不是改它“做什么”，是改它“怎么摆”。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不改变可观察行为&lt;/strong&gt;：用户看到的、API 返回的、数据库写入的，统统不能变。你改了行为，那叫“改功能”或“修 bug”，不叫重构。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可理解性 / 修改成本&lt;/strong&gt;：重构的目标从来不是“代码更好看”这种审美追求，而是&lt;strong&gt;让下一次修改更便宜&lt;/strong&gt;。这是经济学决策，不是艺术决策。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;11-两顶帽子加功能和重构不同时戴&#34;&gt;1.1 两顶帽子：加功能和重构不同时戴&lt;/h3&gt;
&lt;p&gt;Fowler 引用了 Kent Beck 的“两顶帽子”比喻，这是全书我最常拿来讲给同事听的概念：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;戴上“&lt;strong&gt;加功能&lt;/strong&gt;”的帽子：你只添加新代码、新测试，&lt;strong&gt;绝不修改已有代码的结构&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;戴上“&lt;strong&gt;重构&lt;/strong&gt;”的帽子：你只调整结构、保证测试全绿，&lt;strong&gt;绝不添加新功能&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;写代码的每一刻，你头上只能戴一顶帽子。发现“要加这个功能，得先把这段代码理顺”，正确动作是：摘下加功能的帽子 → 戴上重构帽子 → 重构到方便加功能为止 → 换回加功能帽子 → 加功能。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    A[&amp;#34;接到需求：要加新功能&amp;#34;] --&amp;gt; B{&amp;#34;现有代码结构&amp;lt;br/&amp;gt;方便加这个功能吗？&amp;#34;}
    B -- &amp;#34;方便&amp;#34; --&amp;gt; C[&amp;#34;戴加功能的帽子&amp;lt;br/&amp;gt;写新代码 + 新测试&amp;#34;]
    B -- &amp;#34;不方便&amp;#34; --&amp;gt; D[&amp;#34;戴重构的帽子&amp;lt;br/&amp;gt;只做结构调整，测试保持全绿&amp;#34;]
    D --&amp;gt; E{&amp;#34;结构理顺了吗？&amp;#34;}
    E -- &amp;#34;还没有&amp;#34; --&amp;gt; D
    E -- &amp;#34;理顺了&amp;#34; --&amp;gt; C
    C --&amp;gt; F[&amp;#34;提交，帽子归位&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;为什么这么严格？因为&lt;strong&gt;同时做两件事，出了 bug 你分不清是谁引入的&lt;/strong&gt;。重构的每一步都要求测试全绿，如果中途改了行为，红灯亮起时你就失去了判断依据。两顶帽子是重构能保持“小步”的心理学保障：它把一件模糊的、容易失控的事，拆成了两个界限分明的状态。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;2-坏味道清单程序员的启发法清单&#34;&gt;2. 坏味道清单：程序员的启发法清单&lt;/h2&gt;
&lt;p&gt;第 3 章是全书的“字典”——24 种坏味道（Bad Smells）。注意 Fowler 的用词是“味道”不是“罪状”：&lt;strong&gt;闻到味道不代表必须立刻处理，它只是提示你“这里值得看一眼”&lt;/strong&gt;。极端的教条主义本身就是一种味道。&lt;/p&gt;
&lt;p&gt;24 种味道完整清单如下，后面挑最重要的 10 个深入讲：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;神秘命名、重复代码、过长函数、过长参数列表、全局数据、可变数据、发散式变化、霰弹式修改、依恋情结、数据泥团、基本类型偏执、重复的 switch、循环、冗赘的元素、夸夸其谈未来性、临时字段、过长的消息链、中间人、内幕交易、过大的类、异曲同工的类、纯数据类、被拒绝的遗赠、注释。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;21-神秘命名mysterious-name&#34;&gt;2.1 神秘命名（Mysterious Name）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;味道&lt;/strong&gt;：变量叫 &lt;code&gt;data&lt;/code&gt;、函数叫 &lt;code&gt;process&lt;/code&gt;、类叫 &lt;code&gt;Manager&lt;/code&gt;，看完名字不知道它是干嘛的。&lt;/p&gt;
&lt;p&gt;为什么排第一？因为&lt;strong&gt;命名是程序里密度最高的文档&lt;/strong&gt;。一段代码你可能读十次改一次，名字起得好，十次阅读每次都省时间。Fowler 说得直白：如果想不到好名字，往往意味着你对这段逻辑的职责还没想清楚——命名困难本身就是设计问题的信号。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;对应手法&lt;/strong&gt;：改变函数声明（改名）、变量改名、字段改名。现代 IDE 的重命名重构是安全的，改名字没有借口留到下辈子。&lt;/p&gt;
&lt;h3 id=&#34;22-重复代码duplicated-code&#34;&gt;2.2 重复代码（Duplicated Code）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;味道&lt;/strong&gt;：同一段逻辑出现在两个以上的地方。改一处忘另一处，bug 就是这么来的。&lt;/p&gt;
&lt;p&gt;这是所有味道里&lt;strong&gt;危害最确定、修复手法最成熟&lt;/strong&gt;的一个。双胞胎函数用提炼函数收拢；兄弟子类里重复的用函数上移；类似但不完全相同的，先拆出相同的部分再收拢。&lt;/p&gt;
&lt;p&gt;一个工程界的经典注脚：NASA 的航天飞机软件团队以极低缺陷率著称，他们的经验之一就是极端重视消除重复——任何相似代码都必须解释为什么存在。日常项目虽然不必如此严苛，但“重复三次就提炼”的底线值得守住。&lt;/p&gt;
&lt;h3 id=&#34;23-过长函数long-function&#34;&gt;2.3 过长函数（Long Function）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;味道&lt;/strong&gt;：一个函数几十上百行，得靠注释分段才能读懂。&lt;/p&gt;
&lt;p&gt;Fowler 的态度非常激进：&lt;strong&gt;函数就该短&lt;/strong&gt;。短函数的好处是&lt;strong&gt;通过名字传递意图&lt;/strong&gt;——&lt;code&gt;calcOutstanding()&lt;/code&gt; 比一坨内联计算强得多，因为读者看名字就知道意图，需要细节时才钻进去。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;判断标准&lt;/strong&gt;：不是行数，而是“&lt;strong&gt;如果你需要写注释解释一段代码在干嘛，这段代码就该被提炼成函数，函数名用注释里的词&lt;/strong&gt;”。这个标准极其好用，直接把“注释”这个味道变成了提炼函数的路标。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;对应手法&lt;/strong&gt;：提炼函数（Extract Function）——全书使用频率最高的手法，没有之一。第 2 版里 Fowler 把提炼后的函数长度做到五六行甚至两三行，就是要打破“函数太短不值得”的心理障碍。&lt;/p&gt;
&lt;h3 id=&#34;24-过长参数列表long-parameter-list&#34;&gt;2.4 过长参数列表（Long Parameter List）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;味道&lt;/strong&gt;：函数签名里七八个参数，调用处像咒语。&lt;/p&gt;
&lt;p&gt;危害有两个：一是调用方每次都要记参数顺序，极易传错（尤其是同类型相邻参数）；二是参数多往往说明&lt;strong&gt;函数在做不止一件事&lt;/strong&gt;，或者这些参数本该是一个整体。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;对应手法&lt;/strong&gt;：以查询取代参数（能从参数 A 算出参数 B，就别传 B）、保持对象完整（别从对象里掏三个字段传进去，直接传对象）、引入参数对象（见 2.7 数据泥团）、移除标记参数（布尔 flag 参数往往意味着函数里有个 if 分叉，拆成两个函数更干净）。&lt;/p&gt;
&lt;h3 id=&#34;25-全局数据global-data与可变数据mutable-data&#34;&gt;2.5 全局数据（Global Data）与可变数据（Mutable Data）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;全局数据的味道&lt;/strong&gt;：任何地方都能读到、改到它，出了问题你找不到是谁改的。单例本质上也是全局数据。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;可变数据的味道&lt;/strong&gt;：一个变量在很大作用域内被反复赋值，你在第 500 行看到的值，无法信任它声明时的样子。&lt;/p&gt;
&lt;p&gt;这对兄弟是并发 bug 和“灵异 bug”的头号来源。函数式编程这些年崛起的核心卖点之一就是消灭可变状态，和 Fowler 的判断完全同频。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;对应手法&lt;/strong&gt;：封装变量（把全局变量关进函数里，访问必须经过函数）、拆分变量（一个变量被赋两次值干两件事，就拆成两个）、将引用对象改为值对象（值对象不可变，天然免疫一大类 bug）。&lt;/p&gt;
&lt;h3 id=&#34;26-发散式变化与霰弹式修改divergent-change--shotgun-surgery&#34;&gt;2.6 发散式变化与霰弹式修改（Divergent Change / Shotgun Surgery）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;发散式变化&lt;/strong&gt;：&lt;strong&gt;一个类因为不同原因被反复修改&lt;/strong&gt;。“数据库变了改它，报表格式变了也改它，业务规则变了还改它”——这个类承担了太多职责。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;霰弹式修改&lt;/strong&gt;：&lt;strong&gt;一个变化要改很多个类&lt;/strong&gt;。加一个字段类型，DAO 改、Service 改、DTO 改、前端 mock 改，漏一个就炸。&lt;/p&gt;
&lt;p&gt;这俩是&lt;strong&gt;同一枚硬币的两面&lt;/strong&gt;：前者是“多个原因指向一个类”，后者是“一个原因散到多个类”。修复方向相反——发散式变化用&lt;strong&gt;拆分阶段、搬移函数&lt;/strong&gt;把一个类拆开；霰弹式修改用&lt;strong&gt;搬移函数、提炼类&lt;/strong&gt;把散落的逻辑聚到一处。&lt;/p&gt;
&lt;p&gt;判断技巧：想象需求来了，你心里默念“要改哪几个文件”。如果答案是“又是这个巨无霸类”，是发散式变化；如果答案是“天哪又是七八个文件”，是霰弹式修改。&lt;/p&gt;
&lt;h3 id=&#34;27-依恋情结与数据泥团feature-envy--data-clumps&#34;&gt;2.7 依恋情结与数据泥团（Feature Envy / Data Clumps）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;依恋情结&lt;/strong&gt;：一个函数对另一个类的数据兴趣远大于自己所在类的——函数里 &lt;code&gt;order.getCustomer().getAddress().getCity()&lt;/code&gt; 一长串，全是别人家的 getter。治疗方式很直白：&lt;strong&gt;搬移函数&lt;/strong&gt;，让函数搬到它依恋的数据旁边去。“策略”“访问者”模式是例外，它们故意把行为和数据分开。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;数据泥团&lt;/strong&gt;：总是一起出现的三四个字段——&lt;code&gt;startDate&lt;/code&gt;/&lt;code&gt;endDate&lt;/code&gt;、&lt;code&gt;street&lt;/code&gt;/&lt;code&gt;city&lt;/code&gt;/&lt;code&gt;zip&lt;/code&gt;。它们像连体婴儿一样在各个方法签名和类字段里成群结队。&lt;strong&gt;对应手法&lt;/strong&gt;：提炼类，给这团数据一个家（比如 &lt;code&gt;DateRange&lt;/code&gt;、&lt;code&gt;Address&lt;/code&gt;）。提炼完常常有惊喜：散落各处的校验、格式化逻辑（依恋情结）会自然地搬进新类。&lt;/p&gt;
&lt;h3 id=&#34;28-基本类型偏执与重复的-switchprimitive-obsession--repeated-switches&#34;&gt;2.8 基本类型偏执与重复的 switch（Primitive Obsession / Repeated Switches）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;基本类型偏执&lt;/strong&gt;：用字符串存电话号码、用裸 double 存钱、用 int 编码状态。小钱变大钱、状态码幻数满天飞，都是这么来的。&lt;strong&gt;对应手法&lt;/strong&gt;：以对象取代基本类型——一个 &lt;code&gt;Money&lt;/code&gt; 类、&lt;code&gt;PhoneNumber&lt;/code&gt; 类，把校验和格式化的知识收拢进类型系统。业界血案：金额用 float/double 导致的分分钱对不上账，几乎每个支付团队都踩过，标准做法就是专门的 Money 类型或以最小货币单位为单位的整数。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;重复的 switch&lt;/strong&gt;：同一个类型码的 &lt;code&gt;switch&lt;/code&gt;/&lt;code&gt;if-else&lt;/code&gt; 链散落多处。每加一种新类型，得把所有 switch 都找出来改一遍——这是霰弹式修改的特化版。&lt;strong&gt;对应手法&lt;/strong&gt;：&lt;strong&gt;以多态取代条件表达式&lt;/strong&gt;（见 3.6 节）。注意 Fowler 的克制：switch 本身不是罪，&lt;strong&gt;重复的&lt;/strong&gt; switch 才是；如果只有一个 switch 且类型稳定，留着无妨。&lt;/p&gt;
&lt;h3 id=&#34;29-中间人与消息链middle-man--message-chains&#34;&gt;2.9 中间人与消息链（Middle Man / Message Chains）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;中间人&lt;/strong&gt;：一个类一半以上的方法都在委托给别的类，自己什么也不干。封装是好事，但封装到这个程度就是纯粹的过路费。&lt;strong&gt;对应手法&lt;/strong&gt;：移除中间人，让调用方直接和被委托对象说话。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;消息链&lt;/strong&gt;：&lt;code&gt;a.getB().getC().getD()&lt;/code&gt; 一路要下去，调用方对整条链的结构产生了依赖，链中间任何一环变动都会传导过来。这就是“得墨忒耳定律”（Law of Demeter）说的“只和直接朋友说话”。&lt;strong&gt;对应手法&lt;/strong&gt;：隐藏委托关系。&lt;/p&gt;
&lt;p&gt;这一对味道放在一起看很有意思：&lt;strong&gt;过犹不及&lt;/strong&gt;。委托太多是中间人，穿透太多是消息链， Fowler 的词典里没有银弹，只有“闻味道、看剂量”。&lt;/p&gt;
&lt;h3 id=&#34;210-注释comments&#34;&gt;2.10 注释（Comments）&lt;/h3&gt;
&lt;p&gt;注释也上榜了，很多人第一次读会惊讶。Fowler 的逻辑是：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;注释本身不是坏味道，它常常是“ deodorant（除臭剂）”——用来遮盖代码本身的臭。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;当你觉得“这里得写段注释解释一下”，先问：&lt;strong&gt;能不能提炼函数用函数名说清楚？能不能改变量名说清楚？&lt;/strong&gt; 能，就别写注释。注释该写的是“为什么这么做”（业务背景、历史坑、外部约束），而不是“这段代码在干嘛”——后者是代码没写清楚的忏悔。&lt;/p&gt;
&lt;h3 id=&#34;211-其余味道速查表&#34;&gt;2.11 其余味道速查表&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;味道&lt;/th&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;一句话识别&lt;/th&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;首选手法&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;循环（Loops）&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;for 循环里干的事能用管道表达&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;以管道取代循环（filter/map/reduce）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;冗赘的元素（Lazy Element）&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;这个函数/类就只有一层转发，毫无增量&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;内联函数、内联类&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;夸夸其谈未来性（Speculative Generality）&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;“为将来预留”的抽象，现在只有一个实现&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;折叠继承体系、内联&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;临时字段（Temporary Field）&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;某个字段只在特定算法运行时才有值&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;提炼类 + 搬移函数，给它一个家&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;内幕交易（Insider Trading）&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;两个模块私下大量交换数据，耦合紧密&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;搬移函数/字段，减少私聊&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;过大的类（Large Class）&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;一个类字段太多、职责太多&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;提炼类、提炼超类&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;异曲同工的类（Alternative Classes）&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;两个类接口不同干的事一样&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;改变函数声明 + 搬移函数统一接口&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;纯数据类（Data Class）&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;只有字段和 getter/setter，没有行为&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;把操作它的行为搬进来（封装记录）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;被拒绝的遗赠（Refused Bequest）&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;子类继承了一堆用不上的东西&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;函数/字段下移，或以委托取代继承&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;“夸夸其谈未来性”值得单独敲一下黑板：&lt;strong&gt;YAGNI（You Aren&amp;rsquo;t Gonna Need It）&lt;/strong&gt;。每个“将来可能用得上”的抽象，都是要付利息的技术债——它现在就开始收阅读成本，而兑现的那一天往往永远不会来。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;3-核心重构手法七招走天下&#34;&gt;3. 核心重构手法：七招走天下&lt;/h2&gt;
&lt;p&gt;第 6 章起的重构名录有 60 多个手法，但日常高频使用的集中在少数几招。下面七招是我个人使用频率最高、也最能体现“小步安全”思想的。&lt;/p&gt;
&lt;h3 id=&#34;31-提炼函数extract-function&#34;&gt;3.1 提炼函数（Extract Function）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;场景&lt;/strong&gt;：一段代码需要注释才能看懂；或者一个函数太长。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;口诀&lt;/strong&gt;：&lt;strong&gt;意图与实现分离&lt;/strong&gt;——“这段代码做什么”（意图）交给函数名，“怎么做”（实现）交给函数体。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;小步版本&lt;/strong&gt;（Fowler 的名录里每个手法都有这种机械化步骤，这是本书最容易被低估的价值）：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;创建新函数，按意图命名（想不到好名字就先叫临时名，回头再改）。&lt;/li&gt;
&lt;li&gt;把要提炼的代码&lt;strong&gt;复制&lt;/strong&gt;过去（是复制，不是剪切——先留着原件，错了好回退）。&lt;/li&gt;
&lt;li&gt;检查局部变量：被新函数读到的 → 传参；被修改的 → 尽量变成返回值，一次只返回一个。&lt;/li&gt;
&lt;li&gt;编译。在源位置替换为对新函数的调用。&lt;/li&gt;
&lt;li&gt;测试。全绿则提交。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;避坑&lt;/strong&gt;：一次提炼只处理“只读变量多、被改变量少”的块；被改变量超过一个时，先用“拆分变量”或调整结构再提炼。&lt;/p&gt;
&lt;h3 id=&#34;32-内联函数inline-function&#34;&gt;3.2 内联函数（Inline Function）&lt;/h3&gt;
&lt;p&gt;提炼的反操作。&lt;strong&gt;场景&lt;/strong&gt;：函数体已经和函数名一样清楚（&lt;code&gt;getRating() { return moreThanFiveLateDeliveries ? 2 : 1; }&lt;/code&gt; 改名后可能直接内联掉），或者一堆小函数间接层太深、改名/重组前需要先“摊平”。&lt;/p&gt;
&lt;p&gt;重构名录里的手法大多成对出现（提炼/内联、搬入/搬出、封装/暴露），这不是凑数——&lt;strong&gt;可逆性是小步快走的前提&lt;/strong&gt;：这步走错了，反手一步就能退回来。&lt;/p&gt;
&lt;h3 id=&#34;33-搬移函数move-function&#34;&gt;3.3 搬移函数（Move Function）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;场景&lt;/strong&gt;：函数引用别的上下文的元素多于自己所在的上下文（依恋情结的特效药）；或者模块化需要重新划分边界。&lt;/p&gt;
&lt;p&gt;口诀：&lt;strong&gt;逻辑和它操作的数据住在一起&lt;/strong&gt;。搬移时先检查函数引用的所有东西——它们要么一起搬，要么变成参数。搬完旧位置可以留一个委托函数过渡，等调用方都改完再删。&lt;/p&gt;
&lt;h3 id=&#34;34-提炼变量extract-variable&#34;&gt;3.4 提炼变量（Extract Variable）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;场景&lt;/strong&gt;：表达式又长又复杂，比如：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;order&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;quantity&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;order&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;itemPrice&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  Math.&lt;span style=&#34;color:#a6e22e&#34;&gt;max&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;order&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;quantity&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;500&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;order&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;itemPrice&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.05&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  Math.&lt;span style=&#34;color:#a6e22e&#34;&gt;min&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;order&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;quantity&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;order&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;itemPrice&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;100&lt;/span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;逐个起名字：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;basePrice&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;order&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;quantity&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;order&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;itemPrice&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;quantityDiscount&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Math.&lt;span style=&#34;color:#a6e22e&#34;&gt;max&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;order&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;quantity&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;500&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;order&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;itemPrice&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.05&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;shipping&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Math.&lt;span style=&#34;color:#a6e22e&#34;&gt;min&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;basePrice&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;100&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;basePrice&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;quantityDiscount&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;shipping&lt;/span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;零风险、几秒钟、可读性立刻翻倍。&lt;strong&gt;这是性价比最高的重构手法&lt;/strong&gt;，新人最值得先养成这个习惯。变量命名还能顺带自我解释业务规则：原来那 0.05 是批量折扣，0.1 是运费比例。&lt;/p&gt;
&lt;h3 id=&#34;35-封装记录encapsulate-record&#34;&gt;3.5 封装记录（Encapsulate Record）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;场景&lt;/strong&gt;：代码里到处传裸的 object/map/dict（纯数据类的输入版），字段名靠口口相传。&lt;/p&gt;
&lt;p&gt;把记录变成类，访问走方法：字段名写错编译期就炸，而不是线上拿到 &lt;code&gt;undefined&lt;/code&gt;；还能给计算字段提供方法（&lt;code&gt;total&lt;/code&gt; 不必存，用 &lt;code&gt;get total()&lt;/code&gt; 算）。这是 TypeScript 兴起后整个行业都在做的事——&lt;strong&gt;让类型系统替你站岗&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id=&#34;36-以多态取代条件表达式replace-conditional-with-polymorphism&#34;&gt;3.6 以多态取代条件表达式（Replace Conditional with Polymorphism）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;场景&lt;/strong&gt;：同一个类型码的 switch 散落多处（重复的 switch 味道）；或者一个函数内部按类型分叉，分支逻辑还会持续增加。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;步骤&lt;/strong&gt;：为每种类型建子类 → 把条件分支的逻辑搬成子类的覆写方法 → 原函数变抽象。书里的经典例子是“鸟类评级”：按鸟的种类（欧洲燕子、非洲燕子、挪威蓝鹦鹉）分支计算飞行速度和评级，重构后每种鸟一个子类，新增鸟种只需新增一个类——&lt;strong&gt;修改从“找到所有 switch”变成“新建一个文件”&lt;/strong&gt;，这正是开闭原则。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;避坑&lt;/strong&gt;：类型码&lt;strong&gt;稳定且数量少&lt;/strong&gt;时不要硬上多态。switch 只有一处、类型十年不变，多态带来的类爆炸是另一种过度设计。再强调一次：闻味道，看剂量。&lt;/p&gt;
&lt;h3 id=&#34;37-拆分阶段split-phase&#34;&gt;3.7 拆分阶段（Split Phase）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;场景&lt;/strong&gt;：一段代码同时干两件性质不同的事——典型的如“解析输入 + 业务计算 + 输出格式”。&lt;/p&gt;
&lt;p&gt;把两段拆开，中间用一个明确的数据结构传递：第一阶段产出中间对象，第二阶段消费它。好处是&lt;strong&gt;两个阶段的修改互不影响&lt;/strong&gt;——换输出格式不动计算逻辑，换输入来源不动下游。书中 statement 案例（下节）的核心一步就是拆分阶段。命令行工具、编译器（解析→语义分析→代码生成）、数据管道，全是这个手法的工业级应用。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;4-全书导读案例statement-计费重构走查&#34;&gt;4. 全书导读案例：statement 计费重构走查&lt;/h2&gt;
&lt;p&gt;第 2 版第 1 章用 JavaScript 完整走查了一个案例，非常值得复现一遍，因为它是“小步快走”的教科书级演示。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原始代码&lt;/strong&gt;：一个剧团的账单系统，&lt;code&gt;statement(invoice, plays)&lt;/code&gt; 输出客户看戏的账单文本。一个约 40 行的函数，里面一个 switch 按戏剧类型（悲剧/喜剧）算钱，外加积分计算和字符串拼接，全部揉在一起。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;需求来了&lt;/strong&gt;（这就是重构的触发器）：① 账单要支持 HTML 输出；② 剧团打算演更多类型的戏（比如“历史剧”“田园剧”），算钱和积分规则要可扩展。&lt;/p&gt;
&lt;p&gt;Fowler 没有直接动手写 HTML 版本，而是先重构。整个过程的关键步骤：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;先补测试&lt;/strong&gt;：原代码没有任何测试。第一步不是改代码，是给现有行为写一批测试，把输出钉死。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;提炼函数&lt;/strong&gt;：把 switch 算钱的部分提炼成 &lt;code&gt;amountFor(aPerformance)&lt;/code&gt;，把积分计算提炼成 &lt;code&gt;volumeCreditsFor()&lt;/code&gt;。每提炼一个，跑一次测试。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;提炼变量 + 改名&lt;/strong&gt;：&lt;code&gt;thisAmount&lt;/code&gt; 这种模糊名改成 &lt;code&gt;result&lt;/code&gt;，消除不必要的中间变量。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;拆分阶段&lt;/strong&gt;：把“计算账单数据”和“渲染文本账单”拆成两个阶段，中间用一个数据结构传递。此时 &lt;code&gt;statement()&lt;/code&gt; 变成：&lt;code&gt;const data = createStatementData(invoice, plays); return renderPlainText(data);&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;加功能&lt;/strong&gt;：此时写 &lt;code&gt;renderHtml(data)&lt;/code&gt; 只消费中间数据，计算逻辑一行不碰——HTML 版本轻松加上。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;以多态取代条件表达式&lt;/strong&gt;：为“悲剧计算器”“喜剧计算器”建子类，switch 消失。加新戏种 = 加一个新子类。&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    A[&amp;#34;原始代码&amp;lt;br/&amp;gt;一个 40 行大函数&amp;lt;br/&amp;gt;无测试&amp;#34;] --&amp;gt; B[&amp;#34;补测试&amp;lt;br/&amp;gt;钉死现有行为&amp;#34;]
    B --&amp;gt; C[&amp;#34;提炼函数&amp;lt;br/&amp;gt;amountFor / volumeCreditsFor&amp;#34;]
    C --&amp;gt; D[&amp;#34;拆分阶段&amp;lt;br/&amp;gt;计算数据 vs 渲染文本&amp;#34;]
    D --&amp;gt; E[&amp;#34;中间数据结构&amp;lt;br/&amp;gt;statementData&amp;#34;]
    E --&amp;gt; F[&amp;#34;加功能：renderHtml&amp;lt;br/&amp;gt;复用同一份数据&amp;#34;]
    C --&amp;gt; G[&amp;#34;以多态取代条件表达式&amp;lt;br/&amp;gt;TragedyCalculator&amp;lt;br/&amp;gt;ComedyCalculator&amp;#34;]
    G --&amp;gt; H[&amp;#34;加功能：新戏种&amp;lt;br/&amp;gt;只需新增子类&amp;#34;]
    F --&amp;gt; I[&amp;#34;最终结构&amp;lt;br/&amp;gt;好改、好测、好扩展&amp;#34;]
    H --&amp;gt; I&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;这个案例的精髓不在于手法本身（都不难），而在于&lt;strong&gt;节奏&lt;/strong&gt;：40 行的小函数，Fowler 也花了几十个小步、每步跑测试。他要演示的是：&lt;strong&gt;重构的安全性不来自你的技术，来自你的步幅&lt;/strong&gt;。步幅足够小，每一步都可以验证，出错最多浪费一步。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;5-重构与测试先补测试再动刀&#34;&gt;5. 重构与测试：先补测试，再动刀&lt;/h2&gt;
&lt;p&gt;第 2 版相对第 1 版（1999 年）最大的时代变化，就是自动化测试从“推荐”变成了“前提”。Fowler 的态度很明确：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;没有测试保护的重构不叫重构，叫“改代码并祈祷”。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;逻辑链条是这样的：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;重构的定义是“不改变可观察行为”——那你怎么&lt;strong&gt;知道&lt;/strong&gt;行为没变？只能靠测试。&lt;/li&gt;
&lt;li&gt;重构要求小步快走——每一步都要验证，人工验证一遍要十分钟，自动化只要十秒。没有自动化，小步走不起来。&lt;/li&gt;
&lt;li&gt;面对祖传代码没有测试怎么办？&lt;strong&gt;先补测试再动刀&lt;/strong&gt;。补测试本身常常需要先轻量重构（比如提炼函数让依赖可以 mock），这就回到了两顶帽子：戴着测试帽做最小限度的结构调整，只为让测试能跑起来。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Michael Feathers 在《修改代码的艺术》（Working Effectively with Legacy Code）里把“没有测试的代码”直接定义为遗留代码，和 Fowler 互为犄角。两本可以搭配读：Fowler 教你“怎么改”，Feathers 教你“改不动时怎么办”。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    A[&amp;#34;要动一段代码&amp;#34;] --&amp;gt; B{&amp;#34;有测试覆盖吗？&amp;#34;}
    B -- &amp;#34;有&amp;#34; --&amp;gt; C[&amp;#34;跑一遍，确认全绿&amp;#34;]
    B -- &amp;#34;没有&amp;#34; --&amp;gt; D[&amp;#34;最小限度补测试&amp;lt;br/&amp;gt;（为可测性做的微调也算）&amp;#34;]
    D --&amp;gt; C
    C --&amp;gt; E[&amp;#34;做一小步重构&amp;#34;]
    E --&amp;gt; F{&amp;#34;测试全绿？&amp;#34;}
    F -- &amp;#34;绿了&amp;#34; --&amp;gt; G[&amp;#34;立即提交&amp;lt;br/&amp;gt;（小步提交 = 免费回滚点）&amp;#34;]
    F -- &amp;#34;红了&amp;#34; --&amp;gt; H[&amp;#34;撤销这一步&amp;lt;br/&amp;gt;或立刻修复，不要带着红灯继续&amp;#34;]
    G --&amp;gt; E
    H --&amp;gt; E
    E --&amp;gt; I{&amp;#34;目标达成？&amp;#34;}
    I -- &amp;#34;没有，继续小步&amp;#34; --&amp;gt; E
    I -- &amp;#34;达成&amp;#34; --&amp;gt; J[&amp;#34;收工：行为没变，结构更好&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;这张“安全网流程图”里最反直觉也最重要的一条是：&lt;strong&gt;测试红了，默认动作是撤销这一步，而不是往前调&lt;/strong&gt;。小步走意味着你刚刚那一步的范围是已知的、可控的，退回上一步的代价几乎为零；带着红灯继续走，错误范围就不可控了。配合小步提交（每步都 commit），你就拥有了一条随时可以回滚的时间线。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;6-重构的时机与节奏融进日常而不是立项&#34;&gt;6. 重构的时机与节奏：融进日常，而不是立项&lt;/h2&gt;
&lt;h3 id=&#34;61-三次法则与顺手重构&#34;&gt;6.1 三次法则与“顺手重构”&lt;/h3&gt;
&lt;p&gt;Fowler 给的实用规则：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;事不过三，三则重构&lt;/strong&gt;（Rule of Three，源自 Don Roberts）：第一次写就写，第二次重复捏着鼻子忍，第三次必须提炼。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;加功能前重构&lt;/strong&gt;：代码结构碍事，先重构到“加这个功能很容易”，再加功能（两顶帽子）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;修 bug 时重构&lt;/strong&gt;：bug 之所以藏得深，往往因为代码难懂。把 bug 修掉的同时，把“让 bug 藏不住”的结构改出来。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;code review 时重构&lt;/strong&gt;：看到别人的坏味道，当面演示一次提炼函数，比留十句评论有用。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;62-破窗效应与童子军军规&#34;&gt;6.2 破窗效应与童子军军规&lt;/h3&gt;
&lt;p&gt;犯罪学里的“破窗效应”：一栋楼有几扇破窗没人修，很快所有窗户都会被砸——混乱会邀请更多混乱。代码库一模一样：第一处“先这样吧，回头再改”开了头，后面所有人都会照着最烂的那段代码的标准来写。&lt;/p&gt;
&lt;p&gt;对策是 Bob Martin 普及的&lt;strong&gt;童子军军规&lt;/strong&gt;（Boy Scout Rule）：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;离开营地时，要让它比你来时更干净。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;落到代码上：每次提交，都顺手改进一点点——改一个烂名字、提炼一个长函数、删掉一段死代码。不需要翻天覆地，只要&lt;strong&gt;持续为正&lt;/strong&gt;。一个团队做到这点，代码质量会随时间收敛而不是发散。这是对付“祖传代码”唯一被验证过可持续的策略：不是一次性手术，是持续的免疫反应。&lt;/p&gt;
&lt;h3 id=&#34;63-什么时候不重构&#34;&gt;6.3 什么时候&lt;strong&gt;不&lt;/strong&gt;重构&lt;/h3&gt;
&lt;p&gt;书里同样重要的克制：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;代码又烂又永远不用改&lt;/strong&gt;——让它烂着，重构它没有回报。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;重写比重构便宜&lt;/strong&gt;——偶尔成立，但门槛极高，见下节的 Netscape 教训。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;离截止日期太近且重构不能帮上眼前的忙&lt;/strong&gt;——重构的回报在下一次修改，先交付，记技术债，回头还。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;记住：&lt;strong&gt;重构是经济学决策&lt;/strong&gt;。每一步都应该能回答“这能让接下来的修改更便宜吗？”答不上来的重构是自我感动。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;7-大规模重写的教训netscape-之死&#34;&gt;7. 大规模重写的教训：Netscape 之死&lt;/h2&gt;
&lt;p&gt;聊到重构，绕不开软件工程史上最著名的一次“我们不重构了，我们重写”的惨案。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;背景&lt;/strong&gt;：Netscape Navigator 曾统治浏览器市场，但代码库在快速迭代中积重难返。1997 年前后，Netscape 决定&lt;strong&gt;抛弃旧代码，从头重写浏览器引擎&lt;/strong&gt;（即后来的 Gecko / Mozilla 项目）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;结果&lt;/strong&gt;：重写花了将近三年。这三年里 Netscape 几乎没有发布有竞争力的新版本——6.0 版本直到 2000 年底才问世。与此同时，微软的 IE 持续迭代、免费捆绑 Windows，市场份额被彻底翻盘。等 Mozilla 成品出来时，浏览器战争已经结束了。&lt;/p&gt;
&lt;p&gt;Joel Spolsky 在 2000 年那篇著名文章《Things You Should Never Do, Part I》里拿 Netscape 当反面教材，结论斩钉截铁：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;重写是你能对软件做的最糟糕的战略性错误，没有之一。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;他的论证值得背下来：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;旧代码不是垃圾，是知识的化石&lt;/strong&gt;。那些丑陋的补丁，每一个都对应一个真实用户踩过的坑、一个边界情况。重写时你看不见这些知识，于是用户会在新版本里重新踩一遍所有的坑。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;重写期间你无法交付&lt;/strong&gt;。老代码烂归烂，它在生产环境里跑着、赚着钱；新代码在成型之前一文不值。竞争对手不会等你。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;重写通常伴随着“这次一定要设计完美”的野心&lt;/strong&gt;——这正是“夸夸其谈未来性”的企业级版本，工期必然失控。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Fowler 在第 1 版就引用过这个故事来论证：&lt;strong&gt;重构的价值恰恰在于它是重写的替代品&lt;/strong&gt;。用持续的小步重构，让烂代码在“一直保持可交付”的状态下逐步变好，是风险唯一可控的路径。&lt;/p&gt;
&lt;p&gt;当然要有公允的注脚：后来 Mozilla 代码确实成了 Firefox 的底子，长期看那次重写并非全无产出。但&lt;strong&gt;作为商业决策，Netscape 公司没活到收获的那天&lt;/strong&gt;——这正是 Joel 论点最残酷的部分。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;8-带走的清单&#34;&gt;8. 带走的清单&lt;/h2&gt;
&lt;h3 id=&#34;81-核心心智模型五句话&#34;&gt;8.1 核心心智模型（五句话）&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;重构 = &lt;strong&gt;不改变可观察行为&lt;/strong&gt;的结构调整；目标不是美，是&lt;strong&gt;让下次修改更便宜&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;两顶帽子&lt;/strong&gt;：加功能和重构永不同时戴。&lt;/li&gt;
&lt;li&gt;坏味道清单 = 启发法清单：闻到味道 → 查名录 → 用对应手法。味道是提示，不是罪状。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;刷牙，不大扫除&lt;/strong&gt;：小步、频繁、顺手；童子军军规，每次提交留代码更干净一点。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;先补测试，再动刀&lt;/strong&gt;；测试红了先撤销，不带着红灯往前走。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;82-新人避坑指南&#34;&gt;8.2 新人避坑指南&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;坑一：把重构当借口拖延交付。&lt;/strong&gt; 重构是加功能的加速器不是挡箭牌。每次重构前问自己：这步能让手头这个需求更快落地吗？&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;坑二：没有测试就动祖传代码。&lt;/strong&gt; 先补测试。补测试时发现代码不可测，先做最小限度的可测性重构（提炼函数、依赖注入），别一步到位。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;坑三：PR 里混着“重构 + 新功能 + 修 bug”。&lt;/strong&gt; reviewer 无法区分行为变化来自哪里。拆 PR，或者至少拆 commit。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;坑四：看到 switch 就上多态，看到简单类就套设计模式。&lt;/strong&gt; 夸夸其谈未来性也是坏味道。YAGNI。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;坑五：迷信“等我有整块时间再重构”。&lt;/strong&gt; 那一天永远不会来。能融进日常的小重构才有复利，立项式重构大概率变成 Netscape 式豪赌。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;坑六：改名字嫌麻烦。&lt;/strong&gt; IDE 的安全重命名几秒钟一次，这是全软件业性价比最高的操作，没有之一。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;83-一页速查味道--手法&#34;&gt;8.3 一页速查：味道 → 手法&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;mindmap
  root((坏味道&amp;lt;br/&amp;gt;→&amp;lt;br/&amp;gt;重构手法))
    代码冗余
      重复代码 → 提炼函数
      过长函数 → 提炼函数
      过长参数列表 → 引入参数对象
      数据泥团 → 提炼类
      冗赘元素 → 内联函数
    职责错配
      依恋情结 → 搬移函数
      发散式变化 → 拆分阶段
      霰弹式修改 → 搬移函数聚拢
      内幕交易 → 搬移函数减少私聊
      过大的类 → 提炼类
    类型与状态
      基本类型偏执 → 以对象取代基本类型
      重复的switch → 以多态取代条件表达式
      全局数据 → 封装变量
      可变数据 → 拆分变量/值对象
      临时字段 → 提炼类
    意图不清
      神秘命名 → 改名
      注释 → 提炼函数让代码自解释
      复杂表达式 → 提炼变量
      消息链 → 隐藏委托关系
      中间人 → 移除中间人&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;9-写在最后&#34;&gt;9. 写在最后&lt;/h2&gt;
&lt;p&gt;《重构》这本书最被低估的一点：它表面上是一本“手法名录”，实际上是一套&lt;strong&gt;把代码质量从“良心活”变成“机械活”的方法论&lt;/strong&gt;。坏味道给你嗅觉，名录给你机械步骤，测试给你安全网，两顶帽子给你节奏——四样东西配齐，重构就从“需要勇气和天赋的大工程”变成了“不需要下决心就能做的日常小事”。&lt;/p&gt;
&lt;p&gt;而日常小事的复利是惊人的。每天让代码干净 1%，一年后它和自然腐化的代码库就是两个物种。这本书 1999 年出第 1 版、2018 年出第 2 版，横跨二十年依然是软件工程必读，原因无他：&lt;strong&gt;代码会腐化是永恒规律，而刷牙永远有效&lt;/strong&gt;。&lt;/p&gt;
</description>
    </item>
    <item>
      <title>《修改代码的艺术》全书导读：遗留代码不是耻辱，是一张还没铺开的防护网</title>
      <link>https://ftmao.com/blog/bookstore/working-effectively-with-legacy-code-notes/</link>
      <pubDate>Sat, 08 Jul 2023 10:40:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/bookstore/working-effectively-with-legacy-code-notes/</guid>
      <description>&lt;h1 id=&#34;修改代码的艺术全书导读遗留代码不是耻辱是一张还没铺开的防护网&#34;&gt;《修改代码的艺术》全书导读：遗留代码不是耻辱，是一张还没铺开的防护网&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;参考书籍：Michael Feathers《修改代码的艺术》（&lt;em&gt;Working Effectively with Legacy Code&lt;/em&gt;，Prentice Hall 2004，中文版由人民邮电出版社/机械工业出版社先后引进）。
目标读者：没读过这本书、正在或即将维护&amp;quot;别人写的、不敢动的&amp;quot;代码库的程序员。
阅读目标：把全书最锋利的一个定义、一个模型（seam）、一个工作法（characterization test）、一组依赖解除技术讲清楚，最后编译成可执行的日常动作。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;一这本书是什么一个定义就是全书最锋利的一刀&#34;&gt;一、这本书是什么：一个定义就是全书最锋利的一刀&lt;/h2&gt;
&lt;p&gt;这本 2004 年的书，副标题式的宣言只有一个：&lt;strong&gt;遗留代码（legacy code）就是没有测试的代码&lt;/strong&gt;（书中大意）。&lt;/p&gt;
&lt;p&gt;先把成书背景交代一句。2004 年，极限编程和 TDD 刚兴起不久，&amp;ldquo;写新代码时先写测试&amp;quot;的理念已经开始传播，但行业里沉默的大多数面对的现实是：&lt;strong&gt;手头维护的系统写出来的时候还没有单元测试这回事&lt;/strong&gt;。新书都在教&amp;quot;怎么从零写好代码&amp;rdquo;，几乎没人教&amp;quot;怎么面对已经写坏的代码&amp;quot;——而后者才是绝大多数程序员的日常。Feathers 这本书填的就是这个空档，书名里的 Working Effectively 不是修辞：它要解决的问题是&amp;quot;明天就要改这段代码，怎么办&amp;quot;，不是&amp;quot;理想情况下代码应该长什么样&amp;quot;。&lt;/p&gt;
&lt;p&gt;注意这个定义的刁钻之处。我们平常说&amp;quot;遗留代码&amp;quot;，指的是&amp;quot;前人留下的、老旧的、看不懂的代码&amp;quot;——一个时间属性。Feathers 把它换成了一个工程属性：&lt;strong&gt;没有测试覆盖的代码，哪怕昨天刚写完，也已经是遗留代码；有测试覆盖的老代码，反而不遗留。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这一刀切下去，很多习以为常的现象立刻有了解释：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;为什么你昨天写的代码今天就不敢改？因为它从出生起就没有测试，它就是遗留代码。&lt;/li&gt;
&lt;li&gt;为什么有的十年老系统改起来反而顺手？因为核心路径上有测试，行为是可见的。&lt;/li&gt;
&lt;li&gt;为什么&amp;quot;重写&amp;quot;的冲动总是错的？因为重写完的新代码依然没测试的话，你只是生产了一份新的遗留代码。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;书里还有一层更狠的推论（大意）：&lt;strong&gt;没有测试的代码就是坏代码&lt;/strong&gt;——不管它写得多漂亮。因为判断代码好坏的第一标准不是优雅，而是&amp;quot;能不能安全地改&amp;quot;，而安全的前提是你能看见改完之后行为有没有变。看不见行为，优雅就是一张裱在危房里的画。&lt;/p&gt;
&lt;p&gt;这和 &lt;a href=&#34;https://ftmao.com/blog/blog/bookstore/code-complete-learning-notes/&#34;&gt;《代码大全》笔记&lt;/a&gt; 里&amp;quot;软件的首要技术使命是管理复杂度&amp;quot;是同一件事的两面：复杂度管不住，表现就是行为不可观测；行为不可观测，改动就退化成赌博。&lt;/p&gt;
&lt;p&gt;这个定义还直接解开了遗留系统维护的头号死结——书中称之为一个两难困境（大意）：&lt;strong&gt;要安全地改代码，得先有测试；要给代码加测试，往往得先改代码&lt;/strong&gt;（把依赖解开才能放进测试夹具）。鸡生蛋蛋生鸡，多数人卡在这里就放弃了，于是继续 edit and pray。Feathers 给出的破法是这本书全部技术部分的主线：&lt;strong&gt;允许自己做&amp;quot;没有测试看护的最小改动&amp;quot;，但只限于一种改动——为了造出 seam 而做的依赖解除&lt;/strong&gt;。这类改动必须保守到近乎机械（提取一个方法、加一个参数，不碰任何逻辑），换回来的回报是：代码进了测试夹具，之后的一切修改都有了看护。先用最小风险换 leverage，再用 leverage 换安全——这是全书方法论的现金流结构。&lt;/p&gt;
&lt;p&gt;顺带一提这本书的体例：它不是一本&amp;quot;读完放书架&amp;quot;的书，而是一本&lt;strong&gt;工具手册&lt;/strong&gt;。前半本讲心法和工作流（怎么面对改不动的代码），后半本是一本&amp;quot;症状—处方&amp;quot;对照目录（&amp;ldquo;我改不动这个方法，因为它的依赖在构造函数里 new 出来&amp;rdquo;→ 查 Parameterize Constructor）。读法和读 &lt;a href=&#34;https://ftmao.com/blog/blog/posts/design-patterns-intro/&#34;&gt;《设计模式》入门&lt;/a&gt; 一样：第一遍通读建立索引，真正干活时按症状查表。&lt;/p&gt;
&lt;h2 id=&#34;二改代码的两种模式edit-and-pray-vs-cover-and-modify&#34;&gt;二、改代码的两种模式：edit and pray vs cover and modify&lt;/h2&gt;
&lt;p&gt;Feathers 把程序员修改代码的真实状态分成两种模式（书中大意）：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    subgraph 祈祷模式[&amp;#34;edit and pray&amp;lt;br/&amp;gt;编辑后祈祷&amp;#34;]
        A1[&amp;#34;读懂一部分代码&amp;#34;] --&amp;gt; A2[&amp;#34;凭感觉改&amp;#34;]
        A2 --&amp;gt; A3[&amp;#34;编译通过&amp;#34;]
        A3 --&amp;gt; A4[&amp;#34;上线前祈祷&amp;lt;br/&amp;gt;希望没碰坏别处&amp;#34;]
    end
    subgraph 覆盖模式[&amp;#34;cover and modify&amp;lt;br/&amp;gt;覆盖后修改&amp;#34;]
        B1[&amp;#34;找到改动点附近的 seam&amp;#34;] --&amp;gt; B2[&amp;#34;先写测试&amp;lt;br/&amp;gt;罩住现有行为&amp;#34;]
        B2 --&amp;gt; B3[&amp;#34;在测试看护下改代码&amp;#34;]
        B3 --&amp;gt; B4[&amp;#34;测试红了就停&amp;lt;br/&amp;gt;绿了就继续&amp;#34;]
    end&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;两种模式的差别不在技术，在&lt;strong&gt;恐惧的分配方式&lt;/strong&gt;。edit and pray 把恐惧攒到最后一刻一次性兑现——祈祷上线别出事；cover and modify 把恐惧拆成无数次测试运行，每次只消化一小口。&lt;/p&gt;
&lt;p&gt;所以这本书对测试的定位和教科书完全不同：&lt;strong&gt;测试在这里不是 QA 工具，不是质量部门的产出物，而是改动代码的安全网&lt;/strong&gt;。它的价值排序是——先让&amp;quot;改&amp;quot;这个动作变得不恐怖，质量提升是顺带的收益。一个从没写过单元测试的团队，第一本要补的不是&amp;quot;测试覆盖率指标&amp;quot;，而是&amp;quot;下一次改动之前先罩一层网&amp;quot;的习惯。&lt;/p&gt;
&lt;p&gt;书中有句话的意思值得抄在工位上（大意）：我们之所以要在修改前写测试，是因为&lt;strong&gt;在坏代码里，修改带来的伤害往往是延迟发作的&lt;/strong&gt;——你以为只动了 A，其实 B 的行为悄悄变了，三周后才在客户现场爆出来。测试网的作用就是把&amp;quot;三周后&amp;quot;压缩成&amp;quot;三秒后&amp;quot;。&lt;/p&gt;
&lt;p&gt;既然 cover and modify 明显更好，为什么大多数团队还是常年泡在 edit and pray 里？书里给出的解释很实际（大意）：&lt;strong&gt;两种模式的成本结算周期不一样&lt;/strong&gt;。edit and pray 的成本是赊账——改动当下很快，代价在未来某次线上事故里一次性扣掉，而且常常由别人买单；cover and modify 的成本是现付——写测试、解依赖的功夫当下就要掏，收益却摊在之后每一次改动里慢慢回来。人都是短视的，团队也是，所以破局的钥匙不是讲道理，而是&lt;strong&gt;把第一次&amp;quot;现付&amp;quot;的门槛降到足够低&lt;/strong&gt;——这正是后半本依赖解除技术目录存在的意义：它们把&amp;quot;给这段代码加第一个测试&amp;quot;从一天的工程压缩到一小时的手术。&lt;/p&gt;
&lt;p&gt;还有一点容易被误读：这本书讲的测试和 TDD 不是一回事，也不冲突。TDD 是&amp;quot;先写测试驱动出新代码的设计&amp;quot;，往前看；这本书的 characterization test 是&amp;quot;先写测试固化住旧代码的行为&amp;quot;，往后看。一个管新建，一个管存量。&lt;strong&gt;遗留系统里正确的姿势是两条腿走路：存量行为用 characterization test 罩住，新增逻辑用 TDD 长出来&lt;/strong&gt;——两头往中间会师，系统的可测试面积只会越来越大。&lt;/p&gt;
&lt;h2 id=&#34;三seam-模型全书的技术核心&#34;&gt;三、seam 模型：全书的技术核心&lt;/h2&gt;
&lt;p&gt;如果这本书只带走一个词，那就是 &lt;strong&gt;seam（接缝）&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;定义（书中大意）：&lt;strong&gt;seam 是程序中这样一个位置——你不需要修改这段代码本身，就能改变它的行为。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;为什么这个概念是技术核心？因为改遗留代码的全部困难可以归结成一句话：&lt;strong&gt;代码里塞满了硬编码的依赖，你没法把它放进一个测试夹具（test harness）里单独观察。&lt;/strong&gt; 而 seam 就是你在依赖丛林里找到的下刀点——庖丁解牛，顺着纹理下刀，牛的筋骨自然分开；seam 就是代码的纹理。&lt;/p&gt;
&lt;p&gt;Feathers 列出三类 seam（大意）：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    S[&amp;#34;seam&amp;lt;br/&amp;gt;不改代码就能改变行为的点&amp;#34;] --&amp;gt; O[&amp;#34;对象接缝&amp;lt;br/&amp;gt;Object Seam&amp;#34;]
    S --&amp;gt; P[&amp;#34;预处理接缝&amp;lt;br/&amp;gt;Preprocessing Seam&amp;#34;]
    S --&amp;gt; L[&amp;#34;链接接缝&amp;lt;br/&amp;gt;Linking Seam&amp;#34;]
    O --&amp;gt; O1[&amp;#34;例子：把 new 出来的依赖&amp;lt;br/&amp;gt;换成通过参数/子类注入的替身&amp;#34;]
    O --&amp;gt; O2[&amp;#34;适用：Java / C# 等&amp;lt;br/&amp;gt;面向对象语言的主战场&amp;#34;]
    P --&amp;gt; P1[&amp;#34;例子：C/C++ 用宏和条件编译&amp;lt;br/&amp;gt;在测试构建里替换掉某个调用&amp;#34;]
    P --&amp;gt; P2[&amp;#34;适用：有预处理器的语言&amp;lt;br/&amp;gt;测试代码和生产代码两份编译&amp;#34;]
    L --&amp;gt; L1[&amp;#34;例子：换掉链接的库&amp;lt;br/&amp;gt;测试时链接桩版本而不是真库&amp;#34;]
    L --&amp;gt; L2[&amp;#34;适用：编译型语言&amp;lt;br/&amp;gt;把依赖推迟到链接期&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;三种 seam 背后的原理是同一个：&lt;strong&gt;程序的行为由很多个&amp;quot;点&amp;quot;共同决定——调用点、编译点、链接点、对象创建点。每多一个这样的点，就多一次不动手术也能换行为的机会。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;三类 seam 各自的使用场景值得多说一句：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;对象 seam&lt;/strong&gt; 是日常用得最多的。书中反复强调的一个手法（大意）：当一个类在构造函数里 &lt;code&gt;new&lt;/code&gt; 出一个绕不开的依赖（数据库连接、硬件接口、全局单例），你无法实例化它去测试——这时别急着重写，先看能不能造一个 seam：把依赖变成参数传进来，或者子类化后覆写那个获取依赖的方法。&lt;strong&gt;seam 的目标不是好设计，是 leverage（杠杆点）——一个能让你把这块代码抬进测试夹具的支点。&lt;/strong&gt; 设计可以之后再收拾，先拿到支点再说。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;预处理 seam&lt;/strong&gt; 是 C/C++ 程序员的专属武器（大意）：用宏和条件编译，让测试构建里的某个函数调用指向桩实现，生产构建里指向真实实现——同一份源码，两种行为，而源码本身一行没改。它也能在 Java 等语言里以&amp;quot;测试用构建脚本替换类路径&amp;quot;的形式变相存在。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;链接 seam&lt;/strong&gt; 更偏构建层（大意）：把依赖推迟到链接期决定——测试时链接一个桩版本的库，生产时链接真库。它在遗留 C 系统里常常是把整段代码撬进测试夹具的唯一入口。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;理解 seam 之后回头看 &lt;a href=&#34;https://ftmao.com/blog/blog/posts/design-patterns-intro/&#34;&gt;《设计模式》入门笔记&lt;/a&gt; 会有新感觉：工厂、策略、依赖注入这些模式，本质都是在代码里预先埋好的 seam。遗留代码的悲剧不是没有用模式，而是当年没人在乎缝留在哪里。&lt;/p&gt;
&lt;p&gt;书里还给 seam 配了一个搭档概念——&lt;strong&gt;enabling point（使能点，大意）&lt;/strong&gt;：seam 是&amp;quot;行为可以改变的位置&amp;quot;，enabling point 是&amp;quot;你在哪儿做出那个选择&amp;quot;。同一个 seam 可能有多个使能点：一个方法调用是 seam，而选择传哪个对象的使能点，可能在调用处、在构造函数里、在配置文件里。&lt;strong&gt;找 seam 的完整动作是两步：先定位&amp;quot;哪里能换行为&amp;quot;，再决定&amp;quot;在哪儿换最顺手&amp;quot;。&lt;/strong&gt; 很多改不动代码的挫败感，其实来自只做了第一步就动手——缝找到了，但选错了下针的位置。&lt;/p&gt;
&lt;h2 id=&#34;四characterization-test先固化行为再动代码&#34;&gt;四、characterization test：先固化行为，再动代码&lt;/h2&gt;
&lt;p&gt;有了 seam，接下来的问题是：测试写什么？这是全书反直觉的地方。&lt;/p&gt;
&lt;p&gt;教科书说测试是&amp;quot;验证代码的正确性&amp;quot;。Feathers 说不对，在遗留代码场景里你要写的是 &lt;strong&gt;characterization test（特征测试/刻画测试）&lt;/strong&gt;——&lt;strong&gt;它不验证代码&amp;quot;对不对&amp;quot;，它只固化代码&amp;quot;现在是什么样&amp;quot;&lt;/strong&gt;（大意）。&lt;/p&gt;
&lt;p&gt;区别很关键。遗留代码里充满了&amp;quot;看起来是 bug 但业务已经依赖了十年&amp;quot;的行为。你没法站在上帝视角判定哪个行为是&amp;quot;正确&amp;quot;的——你能做的、必须做的，是先把当前行为原样钉在测试里，让任何意外的漂移立刻报警。正确性可以之后讨论，&lt;strong&gt;先保证&amp;quot;变化&amp;quot;这件事是可观测的&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;这个定位和传统的回归测试也不一样。回归测试隐含着&amp;quot;我们曾经知道什么是对的，现在防止它退化&amp;quot;；而面对一段没人说得清意图的遗留代码，你连&amp;quot;对&amp;quot;的基线都没有（大意）。characterization test 的诚实之处在于它承认这一点：&lt;strong&gt;我不知道这段代码做得对不对，但我知道它现在做成什么样，我要保证的是&amp;quot;我改完之后它还是这样&amp;quot;——或者当它必须变的时候，变的那一处是我有意为之的。&lt;/strong&gt; 把&amp;quot;无意的变化&amp;quot;降为零，把&amp;quot;有意的变化&amp;quot;收窄到一处，这就是在遗留代码上干活的全部安全学。&lt;/p&gt;
&lt;p&gt;一个常见疑问：特征测试要写多少才够？书中的回答务实得让人安心（大意）：&lt;strong&gt;不需要覆盖整个系统，只需要覆盖你这次改动的影响面&lt;/strong&gt;。用影响草图圈出&amp;quot;改动会波及哪些行为&amp;quot;，把这个圈里的行为钉住就够了。圈外的代码这次没动，它的行为本来就变不了——真正的风险从来只在影响面之内。这也意味着特征测试是按需生长的：每改一次，网就密一格，从不做&amp;quot;覆盖率大跃进&amp;quot;。&lt;/p&gt;
&lt;p&gt;完整工作法四步：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    A[&amp;#34;① 选 seam&amp;lt;br/&amp;gt;找到改动点附近&amp;lt;br/&amp;gt;能下刀的接缝&amp;#34;] --&amp;gt; B[&amp;#34;② 写观测测试&amp;lt;br/&amp;gt;断言传一个值&amp;lt;br/&amp;gt;跑出什么就钉住什么&amp;#34;]
    B --&amp;gt; C[&amp;#34;③ 改代码&amp;lt;br/&amp;gt;在测试看护下&amp;lt;br/&amp;gt;做最小修改&amp;#34;]
    C --&amp;gt; D[&amp;#34;④ 重构&amp;lt;br/&amp;gt;测试全绿的前提下&amp;lt;br/&amp;gt;顺手收拾结构&amp;#34;]
    D -.-&amp;gt;|&amp;#34;下次改动从这里继续&amp;#34;| A&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;第二步有个很实用的细节（书中大意）：写特征测试时，&lt;strong&gt;断言先写一个你知道多半会失败的值，让测试跑一次，把真实输出抄进断言&lt;/strong&gt;。这不是作弊——你的目的不是证明代码正确，而是用测试当探针，观测代码此刻的行为，然后把观测结果固化下来。测试从&amp;quot;裁判&amp;quot;暂时变成&amp;quot;记录仪&amp;quot;，等它记录够了，才恢复裁判的身份。&lt;/p&gt;
&lt;p&gt;第四步的顺序同样不能乱：&lt;strong&gt;先改出功能、绿了，再重构&lt;/strong&gt;。重构和功能修改混在一次提交里，是遗留代码灾难的头号来源——测试红了，你分不清是功能改错了还是重构挪坏了。一次只动一种东西。&lt;/p&gt;
&lt;p&gt;还有一个贯穿全书的概念，解释了为什么&amp;quot;解依赖&amp;quot;这件事值得花那么大代价（大意）：把代码放进测试夹具，是为了同时获得两种能力——&lt;strong&gt;sensing（感知）和 separation（分离）&lt;/strong&gt;。感知，是你能观测到这段代码的行为，改了之后立刻知道结果变没变；分离，是你能把这段代码从整个系统里拆出来单独跑，不必拖起数据库、硬件、整个应用。很多依赖解除技术表面上是&amp;quot;为了让测试能跑&amp;quot;，实际上是在买这两样东西——sensing 买的是&amp;quot;看得见的胆子&amp;quot;，separation 买的是&amp;quot;快&amp;quot;。一个要跑三分钟的测试你不会常跑，一个不常跑的测试等于没有；所以书中对测试速度的要求近乎苛刻（大意）：&lt;strong&gt;单元测试必须快，慢测试会杀死整个反馈回路&lt;/strong&gt;——哪怕为此要解掉更多依赖。&lt;/p&gt;
&lt;h2 id=&#34;五依赖解除技术族八把常用的小刀&#34;&gt;五、依赖解除技术族：八把常用的小刀&lt;/h2&gt;
&lt;p&gt;全书后半本是一个技术目录，解决同一个问题：&lt;strong&gt;类和方法被依赖焊死了，放不进测试夹具，怎么办&lt;/strong&gt;。挑最常用、回报最快的八个讲，每个都是&amp;quot;一句话场景 + 一句话做法&amp;quot;：&lt;/p&gt;
&lt;p&gt;用之前先建立一个总认知（大意）：&lt;strong&gt;这族技术全部是&amp;quot;保守手术&amp;quot;&lt;/strong&gt;——它们中的大多数可以在没有测试看护的情况下做，因为每一步都机械到几乎不可能改变行为（提取一段代码、加一个参数、改一个可见性）。这不是说它们没有风险，而是说风险被压缩到了&amp;quot;编译器能替你检查&amp;quot;的量级。这是 Feathers 解开第一节那个两难困境的实际手法：用近乎无风险的微改动撬动 seam，seam 撬动测试，测试撬动之后一切真正的改动。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Sprout Method（新生方法）&lt;/strong&gt;：要往一个没法测试的老方法里加逻辑。做法：新逻辑写成一个独立的新方法，老方法里只加一行调用——新方法可以独立测试，老方法只承担一行的风险。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sprout Class（新生类）&lt;/strong&gt;：要加的逻辑需要一堆新状态，塞不进老类。做法：新逻辑整体放进一个新类，老类只持有它的引用并在必要时调用——新类从第一天起就有测试。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Wrap Method（包裹方法）&lt;/strong&gt;：要在老方法每次执行前后加点行为（记日志、发通知），但老方法不能动。做法：把老方法改名藏起来，用原方法名建一个新方法，先做新行为再调旧实现——调用方无感知。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Wrap Class（包裹类）&lt;/strong&gt;：要给整个类的行为外加一层职责（装饰器思路）。做法：新类持有一个老类实例，对外暴露同样的接口，附加行为都在新类里——老类一行不改。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Extract Interface（提取接口）&lt;/strong&gt;：依赖是个具体大类，实例化就要拖起半个系统。做法：把被调用的那几个方法提取成接口，测试里传一个假的实现——接缝造在类型层面。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Extract and Override（提取并覆写）&lt;/strong&gt;：方法中间嵌着一个没法在测试里执行的调用（读硬件、发网络请求）。做法：把那一小段提取成受保护的方法，测试里子类化并覆写成空实现或桩——最小的手术换最大的可测性。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Subclass and Override（子类化并覆写）&lt;/strong&gt;：整个类的构造函数或关键流程里有绕不开的硬依赖。做法：测试里建一个继承它的测试子类，覆写掉获取依赖的方法——不改动生产代码就撬开了行为。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Parameterize Constructor / Method（参数化构造函数/方法）&lt;/strong&gt;：依赖是在内部 &lt;code&gt;new&lt;/code&gt; 出来的，外部完全够不着。做法：把 &lt;code&gt;new&lt;/code&gt; 挪到构造参数或方法参数上，生产代码照常传真实对象，测试传假对象——这是把隐式依赖变显式的标准动作。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这八把刀的共同哲学值得记住：&lt;strong&gt;它们的直接目的都不是&amp;quot;让设计更好&amp;quot;，而是&amp;quot;让代码能被测试&amp;quot;&lt;/strong&gt;。Feathers 把这两件事严格分开（大意）：解依赖是为了找到 leverage，一旦测试夹具立住了，设计的债可以在测试看护下慢慢还。反过来，没有测试就奔着&amp;quot;好设计&amp;quot;去大刀阔斧，那是高级版的 edit and pray。&lt;/p&gt;
&lt;p&gt;这一族技术和 &lt;a href=&#34;https://ftmao.com/blog/blog/bookstore/clean-architecture-book-notes/&#34;&gt;《整洁架构》笔记&lt;/a&gt; 里的依赖规则正好互为镜像：整洁架构讲的是&amp;quot;从第一天起就让依赖指向正确方向&amp;quot;，这本书讲的是&amp;quot;方向已经错了十年，怎么一格一格扳回来&amp;quot;。前者是规划新城，后者是老城改造，工具箱不同，底层都是同一套 seam 思维。&lt;/p&gt;
&lt;p&gt;实战中怎么选刀？有一个朴素的排序原则：&lt;strong&gt;能 Sprout 就不 Wrap，能 Wrap 就不动老代码内部，万不得已才进老代码里做 Extract / Parameterize&lt;/strong&gt;。手术越小，没有测试看护的那一段就越短，风险就越可控。另外书里的技术目录比这八把刀长得多（大意）——还有把参数降为更基础类型的 Primitivize Parameter、把一组方法整体提到抽象基类的 Pull Up Feature 等等——症状不同处方不同，真到动手时值得按&amp;quot;症状&amp;quot;去翻原书后半本的对照目录，这正是它作为工具手册的用法。&lt;/p&gt;
&lt;h2 id=&#34;六巨型类怎么拆一只上千行的怪兽&#34;&gt;六、巨型类：怎么拆一只上千行的怪兽&lt;/h2&gt;
&lt;p&gt;遗留系统里几乎必有一只巨型类——上千行、几十个方法、什么都管。Feathers 给的拆法（大意）分三步：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第一步：职责识别。&lt;/strong&gt; 先别拆，先读。用笨办法给方法分组：把方法按&amp;quot;它们共同操作哪些字段、共同服务哪类调用方&amp;quot;聚类；给私有方法起更长的描述性名字，看名字里反复出现的主题；把类&amp;quot;为谁工作&amp;quot;的所有角色列出来。一个类里往往能数出三五个本可以独立存在的职责——这就是 &lt;a href=&#34;https://ftmao.com/blog/blog/bookstore/code-complete-learning-notes/&#34;&gt;《代码大全》&lt;/a&gt; 说的单一职责，只是这里你是逆着历史考古。&lt;/p&gt;
&lt;p&gt;这一步有几个书中的实用技巧（大意）：一是&lt;strong&gt;看私有方法&lt;/strong&gt;——巨型类里的私有方法往往暗示着一个可以独立出去的职责，因为它们本来就是&amp;quot;类内部的内部&amp;quot;；二是&lt;strong&gt;数调用方&lt;/strong&gt;——如果类的不同方法群服务的是完全不同的调用方（报表模块用这三个方法，同步模块用那五个方法），调用方就是职责的天然分界线；三是&lt;strong&gt;问&amp;quot;这个类在为谁做决定&amp;quot;&lt;/strong&gt;——决策点聚集的地方就是职责所在。这些技巧的共同点是：不依赖你对业务的先验理解，从代码自身的结构里把职责挖出来。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第二步：责任分组。&lt;/strong&gt; 把识别出的职责画成草图：哪个方法群服务于哪个职责，哪些方法脚踩两只船。脚踩两只船的方法是拆分的难点，也是拆分的钥匙——它们往往揭示了两个职责之间真正的边界。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第三步：挤压边界。&lt;/strong&gt; 不是一次性把大类切成五个小类（那是 edit and pray 的豪华版），而是&lt;strong&gt;每次只把一个职责里的一小部分挪出去&lt;/strong&gt;——用 Sprout Class 或提取类的方式，挪一小块，测试看护下跑绿，再挪一小块。原类的边界被一圈一圈挤压收缩，像退潮一样。挤压策略的精髓是：&lt;strong&gt;任何时刻系统都是可运行的，任何一步都是可回退的。&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    G[&amp;#34;巨型类&amp;lt;br/&amp;gt;千行·几十个方法&amp;lt;br/&amp;gt;什么职责都有&amp;#34;] --&amp;gt; S1[&amp;#34;识别职责&amp;lt;br/&amp;gt;方法分组&amp;lt;br/&amp;gt;数出 3~5 个隐藏角色&amp;#34;]
    S1 --&amp;gt; S2[&amp;#34;画责任草图&amp;lt;br/&amp;gt;找到脚踩两只船的方法&amp;lt;br/&amp;gt;那才是真正的边界&amp;#34;]
    S2 --&amp;gt; S3[&amp;#34;每次挪一小块&amp;lt;br/&amp;gt;Sprout Class / 提取类&amp;lt;br/&amp;gt;测试看护下跑绿&amp;#34;]
    S3 --&amp;gt; S4[&amp;#34;边界一圈圈收缩&amp;lt;br/&amp;gt;系统时刻可运行&amp;lt;br/&amp;gt;每步可回退&amp;#34;]
    S4 -.-&amp;gt;|&amp;#34;半年后怪兽小一半&amp;#34;| S3&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;配套的两个心法（大意）：一是拆巨型类时别追求完美边界，先追求&amp;quot;可测试的边界&amp;quot;——边界丑一点没关系，测试网立住了边界可以随时再调；二是拆分过程中行为必须逐字节保持，这是 characterization test 的主场。&lt;/p&gt;
&lt;p&gt;还有一个心理层面的提醒（书中大意）：&lt;strong&gt;拆巨型类的进度要用&amp;quot;季度&amp;quot;来度量，不要用&amp;quot;冲刺&amp;quot;来度量。&lt;/strong&gt; 一个上千行的类是十年业务沉积的结果，指望一个迭代拆完，结局往往是拆到一半需求来了，留下一个比原来更糟的半拆状态。正确的节奏是&amp;quot;每次顺手&amp;quot;：每次因业务需求碰这个类，就顺手把一个职责的一小块挪出去。半年后回头看，怪兽已经小了一半——而没有任何一个迭代被&amp;quot;重构专项&amp;quot;耽误过。&lt;/p&gt;
&lt;h2 id=&#34;七改不动的时候先理解再下刀&#34;&gt;七、改不动的时候：先理解，再下刀&lt;/h2&gt;
&lt;p&gt;书里专门处理了&amp;quot;代码我完全看不懂，但明天必须改&amp;quot;的绝境。在进工具箱之前先记住一个前提（大意）：&lt;strong&gt;&amp;ldquo;改不动&amp;quot;往往不只是代码问题，还是反馈回路问题。&lt;/strong&gt; 如果构建一次要半小时、启动一次要拖起三个中间件，那再好的 seam 和测试也救不了你——反馈慢到一定程度，人就会退回 edit and pray，因为&amp;quot;跑一遍验证&amp;quot;的成本高到不划算。所以书里把&amp;quot;让代码能快速进测试夹具跑起来&amp;quot;当作和&amp;quot;解依赖&amp;quot;同等重要的事：测试夹具的意义就在于绕开整个系统的启动成本，让你在秒级的时间里看到行为变化。工具箱如下（大意）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;画草图，不写文档。&lt;/strong&gt; 读不懂就画：effect sketch（影响草图）——从你要改的方法出发，画出它影响谁、谁影响它，一张图看清代码的&amp;quot;引力范围&amp;rdquo;；feature sketch（特征草图）——按职责给方法分组。草图的价值不在图本身，在于画的过程中你被迫建立了对代码的心智模型。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;在代码上做标注。&lt;/strong&gt; 临时把大方法里的段落抽出来起个名（不提交也行），或者用注释标出&amp;quot;这一段在干嘛&amp;quot;。理解会欺骗记忆，标注不会。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;scratch refactoring（草稿式重构）&lt;/strong&gt;：一条很反直觉的建议——纯粹为了理解代码，可以随手重构它（抽方法、改名、挪位置），&lt;strong&gt;改完扔掉，不提交&lt;/strong&gt;。重构是理解代码最快的探针，比干读效率高得多；扔掉是为了不背负&amp;quot;改坏了&amp;quot;的风险。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;删掉无用代码。&lt;/strong&gt; 死代码是最大的噪音源。确认没被调用的代码就删——版本控制里它永远活着，工作区里它只会碍眼。留下来的每一行都应该是有行为意义的行。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;新代码用 TDD。&lt;/strong&gt; 遗留代码里没有测试不是你不写测试的理由。往遗留系统里加新逻辑时，能用 Sprout 就用 Sprout，新方法新类从第一行起就 TDD——让新长出来的部分永远是&amp;quot;非遗留&amp;quot;的，系统的测试覆盖率会随每次改动自然上涨。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;小步快走。&lt;/strong&gt; 每一步改动之间跑一次测试，绿了就存盘。步子小到&amp;quot;这一步不可能错&amp;quot;的程度，速度反而最快——因为省去了所有&amp;quot;改了一下午发现早就坏了、回滚到不知道哪个版本&amp;quot;的灾难恢复时间。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这套&amp;quot;先理解&amp;quot;的策略和 &lt;a href=&#34;https://ftmao.com/blog/blog/bookstore/code-complete-learning-notes/&#34;&gt;《代码大全》学习笔记&lt;/a&gt; 里的调试心法同源：&lt;strong&gt;理解是修改的前置条件，而理解本身是可以工程化的&lt;/strong&gt;——草图、标注、scratch refactoring，都是把&amp;quot;读代码&amp;quot;从玄学变成动作的工具。&lt;/p&gt;
&lt;p&gt;这个工具箱里我最想单独强调 scratch refactoring，因为它违反了多数人的直觉：花两小时重构一份代码然后全部扔掉，听起来是纯浪费。但它的账要这么算——你需要的从来不是&amp;quot;重构的结果&amp;quot;，而是&amp;quot;重构过程中被迫看清的结构&amp;quot;。干读代码是被动接收，读三小时可能还是一团雾；动手重构是主动解剖，抽第三个方法的时候你就已经知道这段代码的骨架长什么样了。&lt;strong&gt;理解是这次任务的真实交付物之一&lt;/strong&gt;，代码只是理解的副产品——既然是副产品，扔掉并不可惜，版本库里那份没动过的才是资产。这和写文档之前先画草图、做方案之前先写 spike 是同一个道理：用小成本的可丢弃物，换大决策的确定性。&lt;/p&gt;
&lt;h2 id=&#34;八全书心法遗留代码是资产恐惧来自看不见&#34;&gt;八、全书心法：遗留代码是资产，恐惧来自看不见&lt;/h2&gt;
&lt;p&gt;读到最后会发现，这本技术书的底层是一篇心理学论文。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;遗留代码不是耻辱，是资产。&lt;/strong&gt; 它丑、它乱、它没人敢碰——但它在生产环境跑了十年，每一行丑陋的代码背后都可能固化着一次真实业务的教训、一个已经交付的需求、一个被客户依赖的行为。重写派看见的是债，Feathers 看见的是&lt;strong&gt;已经兑付过的价值&lt;/strong&gt;。正确的心态不是&amp;quot;这破代码该推倒&amp;quot;，而是&amp;quot;这份资产缺一张安全网，我来给它铺上&amp;quot;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;恐惧的来源是看不见行为。&lt;/strong&gt; 程序员面对遗留代码的恐惧，本质上和走夜路的恐惧同构——不是路难走，是看不见。characterization test 做的事情，就是把行为从&amp;quot;隐式的、存在于某次生产事故记忆里的&amp;quot;变成&amp;quot;显式的、写在断言里的&amp;quot;。行为一旦可见，恐惧就有了边界，改动就有了底气。这和我在方法论里反复说的&amp;quot;写下来&amp;quot;是同构的：&lt;strong&gt;不可见的东西统治你，可见的东西服务你。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;整本书的刀法，用庖丁解牛来对照最贴切：庖丁十九年解数千牛而刀刃若新发于硎，靠的不是力气，是&amp;quot;以无厚入有间&amp;quot;——找到牛体的天然纹理，顺着纹理下刀。&lt;strong&gt;seam 就是代码的纹理，characterization test 就是下刀前先摸清纹理的那只手。&lt;/strong&gt; 对遗留代码使蛮力的人，三年换一把刀；会找 seam 的人，刀越用越快。&lt;/p&gt;
&lt;p&gt;还有一层书里没有大声说、但全书处处都在暗示的心法：&lt;strong&gt;在遗留系统里，第一个写测试的人改变的不是代码，是团队的物理环境。&lt;/strong&gt; 一张测试网一旦存在，后来者改代码的默认姿势就变了——从&amp;quot;祈祷&amp;quot;变成&amp;quot;跑一遍看看&amp;quot;。安全网和恐惧一样会传染，方向相反而已。所以不要等&amp;quot;团队有了测试文化&amp;quot;才开始写测试，顺序是反的：你写下的第一个 characterization test，就是测试文化的第一个细胞。这也是&amp;quot;资产&amp;quot;视角的推论——测试网是极少数&lt;strong&gt;只涨不跌&lt;/strong&gt;的工程资产：它一旦固化住一个行为，就永远为之后所有的改动站岗，而且每新增一条断言，存量价值的保护范围就扩大一格。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    F[&amp;#34;面对遗留代码的恐惧&amp;lt;br/&amp;gt;看不见行为，不敢改&amp;#34;] --&amp;gt; T[&amp;#34;characterization test&amp;lt;br/&amp;gt;固化当前行为&amp;lt;br/&amp;gt;让不可见变可见&amp;#34;]
    T --&amp;gt; S[&amp;#34;seam&amp;lt;br/&amp;gt;找到下刀的纹理&amp;lt;br/&amp;gt;获得 leverage&amp;#34;]
    S --&amp;gt; D[&amp;#34;依赖解除技术族&amp;lt;br/&amp;gt;Sprout / Wrap / Extract&amp;lt;br/&amp;gt;Subclass / Parameterize&amp;#34;]
    D --&amp;gt; R[&amp;#34;在测试看护下&amp;lt;br/&amp;gt;改代码 + 挤压边界&amp;#34;]
    R --&amp;gt; C[&amp;#34;遗留代码逐步变成&amp;lt;br/&amp;gt;有测试覆盖的资产&amp;#34;]
    C -.-&amp;gt;|&amp;#34;恐惧被切成小份消化&amp;#34;| F&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h2 id=&#34;九这本书值不值得读&#34;&gt;九、这本书值不值得读&lt;/h2&gt;
&lt;p&gt;把全书压成一张图，就是&amp;quot;定义 → 模型 → 工作法 → 工具箱&amp;quot;四层：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;一个定义&lt;/strong&gt;：遗留代码 = 没有测试的代码。它把&amp;quot;遗留&amp;quot;从时间属性换成工程属性，顺带解释了为什么新写的代码也会一夜之间变遗留。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;一个模型&lt;/strong&gt;：seam。不改代码就能改变行为的点，是一切依赖解除技术的解剖学基础。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;一个工作法&lt;/strong&gt;：characterization test 四步——选 seam、写观测测试、改代码、重构。把&amp;quot;修改遗留代码&amp;quot;从赌博变成流程。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;一个工具箱&lt;/strong&gt;：Sprout / Wrap / Extract / Subclass / Parameterize 一族依赖解除技术，按症状查表使用。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;优点很明确：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;问题定义得准&lt;/strong&gt;。二十多年了，&amp;ldquo;遗留代码 = 没有测试的代码&amp;quot;依然是这个领域最锋利的一刀，没有之一。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;技术可执行&lt;/strong&gt;。后半本的症状—处方目录不是空谈原则，每一条都能当天上手——这在软件工程书里并不多见。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;心态建设到位&lt;/strong&gt;。它把&amp;quot;面对烂代码的羞耻和恐惧&amp;quot;也当成工程问题处理，给出的解法是&amp;quot;让行为可见&amp;rdquo;，而不是&amp;quot;你要勇敢&amp;quot;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;局限也要说清楚：成书于 2004 年，例子语言以 Java/C++/C# 为主，链接 seam、预处理 seam 这些章节对纯动态语言程序员参考价值有限；书中还没有依赖注入容器、mock 框架、容器化测试环境这些今天的标配，很多当年需要手工解的依赖，现在框架已经替你解了。但它的底层模型——seam、leverage、sensing 与 separation——不过时，因为&lt;strong&gt;依赖问题不过时&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;推荐给两类人：正在维护没测试的老系统的工程师（直接读全书，后半本放案头当手册）；写代码从不写测试、想建立测试习惯的工程师（重点读前半本，先理解&amp;quot;测试是安全网&amp;quot;这个定位再谈技术）。&lt;/p&gt;
&lt;p&gt;阅读顺序上给一个实操建议：第一遍只读前半本，读到 characterization test 四步为止就停手去干活——找一个你正在维护的模块，完整地走一遍&amp;quot;找 seam → 写特征测试 → 在测试看护下改&amp;quot;，走完一遍再回来翻后半本的技术目录。这本书的全部价值都在&amp;quot;用&amp;quot;里，纯读完不用，三个月后只剩&amp;quot;遗留代码 = 没测试的代码&amp;quot;这一句口号。&lt;/p&gt;
&lt;h2 id=&#34;落地编译成工程师的日常动作&#34;&gt;落地：编译成工程师的日常动作&lt;/h2&gt;
&lt;p&gt;把这本书的方法论编译成日常动作，不用立志，只要执行五条。这五条的共同点是&lt;strong&gt;都不需要任何立项、任何批准、任何&amp;quot;重构专项&amp;quot;&lt;/strong&gt;——它们全部可以藏在日常的需求开发里完成，这正是 Feathers 式改良主义的精髓：不跟管理层要时间还债，而是让每次还债都搭在真实需求的便车上。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;接遗留系统的第一件事不是改代码，是找 seam、写特征测试。&lt;/strong&gt; 接到任何维护型任务，先把&amp;quot;我要改的地方能不能放进测试夹具&amp;quot;作为开工的门槛。放不进去就先解依赖——磨刀的时间不是浪费，是这笔任务里 ROI 最高的部分。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;第二次祈祷式修改就停下来补安全网。&lt;/strong&gt; 第一次 edit and pray 可以是形势所迫；同一个模块第二次还想凭感觉改，就是明确的信号——停下来，给这个模块写 characterization test，把网补上再走。恶习都是在&amp;quot;就这一次&amp;quot;里养成的。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;新逻辑一律 Sprout，从第一行起可测。&lt;/strong&gt; 往没测试的老代码里加功能时，默认走 Sprout Method / Sprout Class：老代码只加一行调用，新逻辑活在有测试的新方法新类里。让系统的&amp;quot;非遗留面积&amp;quot;随每次提交单调递增。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;评审时多问一句：这次改动的防护网在哪？&lt;/strong&gt; Code review 除了看 diff 本身，固定问一句&amp;quot;这个行为现在有测试钉住吗&amp;quot;。没有，就讨论要不要当场补——这句话问上一年，团队的测试文化就长出来了。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;重构与改功能，永远不放在同一次提交。&lt;/strong&gt; 一次只动一种东西：要么纯重构（行为逐字节不变，测试不该红），要么纯改功能（在已绿的测试看护下改）。混在一起出问题时分不清归因，等于主动放弃了测试网的保护。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;一句话总结这本书：&lt;strong&gt;测试不是成本，是让你敢动代码的许可证；seam 不是技巧，是代码的解剖学。&lt;/strong&gt; 读完它再看任何一个老系统，你看到的不再是&amp;quot;一坨烂代码&amp;quot;，而是一张还没铺开的防护网，和网上一个个等着被找到的接缝。&lt;/p&gt;
&lt;p&gt;最后再补一句这本书在我心里的真实位置：它不是&amp;quot;教你写测试&amp;quot;的书，而是&lt;strong&gt;教你在不信任代码的环境里重建信任&lt;/strong&gt;的书——对代码的信任、对改动的信任、最终是对自己手艺的信任。这份信任的重建方法，二十年来没有变过：让行为可见，让变化可测，让每一步可回退。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;相关阅读：&lt;a href=&#34;https://ftmao.com/blog/blog/bookstore/code-complete-learning-notes/&#34;&gt;《代码大全》学习笔记&lt;/a&gt; · &lt;a href=&#34;https://ftmao.com/blog/blog/bookstore/clean-architecture-book-notes/&#34;&gt;《整洁架构》读书笔记&lt;/a&gt; · &lt;a href=&#34;https://ftmao.com/blog/blog/posts/design-patterns-intro/&#34;&gt;设计模式入门&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;这三篇和本文正好凑成一条线：设计模式教你&amp;quot;缝应该留在哪&amp;quot;，整洁架构教你&amp;quot;依赖应该指向哪&amp;quot;，代码大全教你&amp;quot;复杂度应该怎么管&amp;quot;，而本文回答的是那个最狼狈也最常见的问题——前面这些都没做到，系统已经在手里了，明天还要改，怎么办。&lt;/p&gt;
</description>
    </item>
    <item>
      <title>《程序员修炼之道》读书笔记：把五种逻辑能力焊进日常工程动作</title>
      <link>https://ftmao.com/blog/bookstore/pragmatic-programmer-notes/</link>
      <pubDate>Mon, 05 Sep 2022 16:00:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/bookstore/pragmatic-programmer-notes/</guid>
      <description>&lt;h1 id=&#34;程序员修炼之道读书笔记把五种逻辑能力焊进日常工程动作&#34;&gt;《程序员修炼之道》读书笔记：把五种逻辑能力焊进日常工程动作&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;逻辑思维书单合读笔记的收官篇。前两篇：《&lt;a href=&#34;https://ftmao.com/blog/blog/bookstore/asking-questions-pyramid-principle-book-notes/&#34;&gt;〈学会提问〉与〈金字塔原理〉&lt;/a&gt;》（想清楚 + 说明白）、《&lt;a href=&#34;https://ftmao.com/blog/blog/bookstore/how-to-solve-it-thinking-in-systems-notes/&#34;&gt;〈怎样解题〉与〈系统之美〉&lt;/a&gt;》（解点状问题 + 看系统结构）；总纲见《&lt;a href=&#34;https://ftmao.com/blog/blog/posts/programmer-logical-thinking-training/&#34;&gt;程序员逻辑思维训练手册&lt;/a&gt;》。&lt;br&gt;
《程序员修炼之道》（The Pragmatic Programmer）在这个书单里位置特殊：前四本书练的是「脑」，这本书练的是「手」——&lt;strong&gt;它把抽象的思维能力，翻译成 DRY、正交性、曳光弹这样一条一条可以当天就用起来的工程纪律。&lt;/strong&gt;&lt;br&gt;
这篇笔记不按原书章节抄，按训练手册里的五种能力重新归类。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    BOOK[&amp;#34;《程序员修炼之道》&amp;lt;br/&amp;gt;几十条 tips 散在十章里&amp;#34;] --&amp;gt; A[&amp;#34;概念清晰&amp;lt;br/&amp;gt;DRY / 命名 / 需求之坑&amp;#34;]
    BOOK --&amp;gt; B[&amp;#34;推理链条&amp;lt;br/&amp;gt;橡皮鸭调试 / 不要假定要证明&amp;lt;br/&amp;gt;拒绝巧合式编程&amp;#34;]
    BOOK --&amp;gt; C[&amp;#34;抽象分层&amp;lt;br/&amp;gt;正交性 / 得墨忒耳法则&amp;lt;br/&amp;gt;解耦与时间耦合&amp;#34;]
    BOOK --&amp;gt; D[&amp;#34;边界意识&amp;lt;br/&amp;gt;契约式设计 / 断言&amp;lt;br/&amp;gt;防御式编程 / 资源有始有终&amp;#34;]
    BOOK --&amp;gt; E[&amp;#34;因果辨析&amp;lt;br/&amp;gt;曳光弹与原型 / 估算&amp;lt;br/&amp;gt;重构的时机&amp;#34;]
    style BOOK fill:#e8f4e8&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;能力一-概念清晰dry-的本质不是别复制粘贴&#34;&gt;能力一 概念清晰：DRY 的本质不是「别复制粘贴」&lt;/h2&gt;
&lt;p&gt;书中被引用最多也误解最深的一条是 DRY（Don&amp;rsquo;t Repeat Yourself）。它说的不是「代码别重复」，而是——&lt;strong&gt;每一个知识点，在系统中必须有唯一、权威、无歧义的表示&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;踩坑故事 #1（三处复制的校验逻辑，一次资损）&lt;/strong&gt;：订单金额的校验规则（起购金额、优惠券叠加上限）在三个服务里各写了一份。规则调整时改了两处漏了第三处，一个边缘渠道绕过了叠加上限，被羊毛党薅了一晚上。复盘时发现三份代码&lt;strong&gt;长得并不一样&lt;/strong&gt;——不是复制粘贴，是同一个「知识」被各自实现了三遍。DRY 管的就是这个：校验规则收敛成一份配置 + 一个共享库，三处引用。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;踩坑故事 #2（需求之坑：做了用户「说要的」，不是「要的」）&lt;/strong&gt;：运营说「要一个导出全量订单的按钮」。我们做了，导出 800 万行 Excel，系统卡死，运营崩溃。追问之下，真实需求是「每周一看上周各渠道的退款率」——一张定时推送的报表就能解决。书中的原话值得抄在墙上：&lt;strong&gt;「需求很少躺在表面上，它们深埋在层层假设、误解和政治之下。」&lt;/strong&gt;「我们要 X」这个陈述本身，就是需要被挖掘的假设。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;实践清单：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; 评审时先问：这个「需求」背后要解决的问题是什么？（至少往下挖一层）&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; 同一个规则/知识在系统里有几处表示？超过一处就要收敛&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; 名字说得出准确含义吗？说不清含义的命名（&lt;code&gt;data&lt;/code&gt;、&lt;code&gt;temp&lt;/code&gt;、&lt;code&gt;handle&lt;/code&gt;）是概念没清晰的信号&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;能力二-推理链条调试的哲学与巧合式编程&#34;&gt;能力二 推理链条：调试的哲学与「巧合式编程」&lt;/h2&gt;
&lt;p&gt;书里调试章节只有两条核心纪律：&lt;strong&gt;「不要恐慌」&lt;strong&gt;和&lt;/strong&gt;「不要假定，要证明」（Don&amp;rsquo;t Assume It — Prove It）&lt;/strong&gt;。配合著名的橡皮鸭调试法（把问题逐行讲给一只鸭子听，讲着讲着自己就找到了答案），构成一套完整的 debug 心法——和波利亚的四步法互为表里。&lt;/p&gt;
&lt;p&gt;它给一种常见病起了名字：&lt;strong&gt;巧合式编程（Programming by Coincidence）&lt;/strong&gt;——代码能跑，但你不知道为什么能跑；改了一行，测试过了，至于为什么过，不知道。这种代码的每次成功运行都是运气贷的款，利率很高。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;踩坑故事 #3（「能跑就行」的代码，凌晨随机爆炸）&lt;/strong&gt;：接过一段遗留代码：发送消息后 sleep 500ms 再查状态，「因为不 sleep 偶尔查不到」。没人知道 500ms 是怎么来的——它就是巧合式编程的活标本：把「碰巧没失败」当成了「正确」。大促流量上来，下游延迟抖动到 600ms，状态查询大面积误判，凌晨两点报警群爆炸。根因是消息状态要轮询或回调确认，sleep 是用运气对抗时延。&lt;strong&gt;凡是「不知道为什么能跑」的代码，上线前必须弄懂，或者重写。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;踩坑故事 #4（橡皮鸭的实战版）&lt;/strong&gt;：一个诡异的并发 bug 查了两天，最后是在给同事复述问题的时候自己发现的——讲到「这个计数器只在这里加……」突然意识到「不对，还有一个地方也加」。语言化会强迫推理补全所有跳步。&lt;strong&gt;讲不出来 = 没想完整&lt;/strong&gt;，这是橡皮鸭有效的底层原因。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;实践清单：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; 修复前能一句话说清根因吗？说不清就还没找到&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; 代码里有没有「不知道为什么能跑」的部分？找出来，弄懂或重写&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; 卡住超过 30 分钟？找人（或 AI）把问题从头讲一遍&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; 修复后问：这个 bug 依赖的「巧合」还有哪些？（同类巧合一起清掉）&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;能力三-抽象分层正交性改-a-不坏-b的工程学&#34;&gt;能力三 抽象分层：正交性，「改 A 不坏 B」的工程学&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;正交性&lt;/strong&gt;：两个组件，改一个不影响另一个，就叫正交。这是全书对「分层」最硬核的定义——不是画几张架构图，是「改动的爆炸半径有多小」。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    subgraph BAD[&amp;#34;非正交：牵一发动全身&amp;#34;]
        A1[&amp;#34;业务逻辑&amp;#34;] --- A2[&amp;#34;日志组件&amp;#34;]
        A2 --- A3[&amp;#34;配置格式&amp;#34;]
        A3 --- A1
        A1 -.-&amp;gt;|&amp;#34;改日志库版本&amp;lt;br/&amp;gt;业务发布受阻&amp;#34;| X1[&amp;#34;爆炸半径: 全系统&amp;#34;]
    end
    subgraph GOOD[&amp;#34;正交：各改各的&amp;#34;]
        B1[&amp;#34;业务逻辑&amp;#34;] --&amp;gt; B4[&amp;#34;稳定接口&amp;#34;]
        B2[&amp;#34;日志组件&amp;#34;] --&amp;gt; B4
        B3[&amp;#34;配置&amp;#34;] --&amp;gt; B4
        B4 -.-&amp;gt;|&amp;#34;改动被接口吸收&amp;#34;| X2[&amp;#34;爆炸半径: 单组件&amp;#34;]
    end
    style GOOD fill:#e8f4e8&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;配套的两条：&lt;strong&gt;得墨忒耳法则&lt;/strong&gt;（只跟直接朋友说话，别 &lt;code&gt;a.getB().getC().doSomething()&lt;/code&gt; 层层穿透）和&lt;strong&gt;时间耦合&lt;/strong&gt;（A 必须发生在 B 之前？那顺序本身就是依赖，要显式化或消除）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;踩坑故事 #5（升级日志库，阻塞了业务发版）&lt;/strong&gt;：安全部门要求全公司升级某个日志组件版本。我们的业务代码里直接 import 了它的十几个类，牵一发动全身，业务迭代被迫停了一周。有防腐层的团队半天就升完了。&lt;strong&gt;依赖第三方时永远隔一层自己的接口&lt;/strong&gt;——这是正交性最朴素的形式，也是最常被偷懒跳过的一层。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;实践清单：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; 问每个改动：它的爆炸半径到哪里？（能答上来，说明边界是清楚的）&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; 代码里有没有 &lt;code&gt;a.b.c.d()&lt;/code&gt; 式穿透调用？有就收口&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; 有没有「必须先调 X 再调 Y」的隐性顺序？显式化（状态机/工作流）或消除&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; 第三方依赖隔了自己的接口层吗？&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;能力四-边界意识契约断言与防御式编程&#34;&gt;能力四 边界意识：契约、断言与防御式编程&lt;/h2&gt;
&lt;p&gt;书中的边界三板斧：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;契约式设计&lt;/strong&gt;：每个方法声明「调用前你必须保证什么（前置条件），我保证返回什么（后置条件），过程中什么不会变（不变式）」&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;断言&lt;/strong&gt;：「不可能发生」的情况就用断言挡住——&lt;strong&gt;它不可能发生的意思是，如果发生了，一定是 bug，要让系统立刻响铃&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;资源有始有终&lt;/strong&gt;：谁分配谁释放，成对出现&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;踩坑故事 #6（「不可能为空」的字段，空了）&lt;/strong&gt;：订单下游默认「支付成功的订单必有支付流水号」，没做校验。一次支付渠道故障，回调丢了流水号但状态是「成功」，空流水号一路穿透到对账任务，对账差异报警在三天后才被人注意到。如果入口有一条断言，事故时长是三分钟。&lt;strong&gt;「不可能」三个字是边界意识最危险的敌人——线上专治各种不可能。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;踩坑故事 #7（资源泄漏：谁打开的连接没还）&lt;/strong&gt;：压测时连接数缓慢上涨，八小时后服务假死。定位到一个异常分支里 &lt;code&gt;connection.close()&lt;/code&gt; 被跳过——正常路径成对，异常路径裸奔。教训和书中的完全一致：&lt;strong&gt;资源的分配与释放要在同一个视野里可见&lt;/strong&gt;（try-with-resources / defer / RAII），跨函数传递所有权的设计，十个里有九个会漏。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;实践清单：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; 关键方法的契约写得出来吗？（前置/后置/不变式各一句）&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; 「不可能发生」的假设有没有断言守着？&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; 外部输入（用户、上游、回调）全部按「不可信」校验了吗？&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; 资源的打开和关闭在同一个代码块视野内吗？&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;能力五-因果辨析曳光弹估算与重构的时机&#34;&gt;能力五 因果辨析：曳光弹、估算与重构的时机&lt;/h2&gt;
&lt;p&gt;这一组 tips 回答的是「决策」问题——怎么在信息不足时往前推进：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    subgraph TRACER[&amp;#34;曳光弹 Tracer Bullets&amp;#34;]
        T1[&amp;#34;端到端打通一条最薄链路&amp;lt;br/&amp;gt;登录→下单→支付 各只实现 5%&amp;#34;] --&amp;gt; T2[&amp;#34;架构/集成/部署&amp;lt;br/&amp;gt;风险第一时间暴露&amp;#34;]
        T2 --&amp;gt; T3[&amp;#34;之后每一枪&amp;lt;br/&amp;gt;都沿着弹道修正&amp;#34;]
    end
    subgraph PROTO[&amp;#34;原型 Prototype&amp;#34;]
        P1[&amp;#34;用纸板/脚本验证&amp;lt;br/&amp;gt;某个具体问题&amp;#34;] --&amp;gt; P2[&amp;#34;验证完就扔掉&amp;lt;br/&amp;gt;代码使命是学习&amp;#34;]
    end
    Q[&amp;#34;面对一个新系统&amp;#34;] --&amp;gt; TRACER
    Q --&amp;gt; PROTO
    style TRACER fill:#e8f4e8&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;曳光弹 vs 原型&lt;/strong&gt;是书中最精妙的区分：原型是「用后即弃的实验」，回答局部问题；曳光弹是「会一直发光的骨架」，端到端打通整条链路，让集成风险第一天就暴露。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;踩坑故事 #8（三个月大设计，集成第一天就崩）&lt;/strong&gt;：早年一个项目，前两个月各模块分头开发，接口全靠文档约定。联调第一天：认证方式对不上、时间格式两套、错误码语义相反——三个月的工期，联调吃掉一个月。后来所有新项目第一周的硬指标改成：&lt;strong&gt;端到端曳光弹必须打通&lt;/strong&gt;（登录→一个空接口→落库→前端显示一个 hello），哪怕薄如蝉翼。集成风险从「最后一个月的惊喜」变成「第一周的日常」。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;估算&lt;/strong&gt;一节也值得单独记：估算前先问「精度要求是什么」——「两周左右」和「14 个工作日」是两种承诺；估算要带上依据（「参照上次的 X 项目」），否则就是报数字占卜。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;实践清单：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; 新项目第一周：曳光弹打通了吗？（端到端，再薄也算）&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; 验证性代码标记「原型」了吗？原型有没有被当成产品上线？（常见事故）&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; 估算带依据和精度范围了吗？&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; 重构是「顺手做」（童子军军规：离开时比来时干净）还是攒成了「重构专项」？后者通常永远不会排期&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;收官五本书拼起来是一张完整的图&#34;&gt;收官：五本书拼起来是一张完整的图&lt;/h2&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    A[&amp;#34;《学会提问》&amp;lt;br/&amp;gt;结论要过安检&amp;#34;] --&amp;gt; THINK[&amp;#34;想清楚&amp;#34;]
    B[&amp;#34;《金字塔原理》&amp;lt;br/&amp;gt;结论站塔尖&amp;#34;] --&amp;gt; SAY[&amp;#34;说明白&amp;#34;]
    C[&amp;#34;《怎样解题》&amp;lt;br/&amp;gt;四步解题法&amp;#34;] --&amp;gt; SOLVE[&amp;#34;解得掉&amp;#34;]
    D[&amp;#34;《系统之美》&amp;lt;br/&amp;gt;找结构不找人&amp;#34;] --&amp;gt; SOLVE
    E[&amp;#34;《程序员修炼之道》&amp;lt;br/&amp;gt;工程纪律清单&amp;#34;] --&amp;gt; DO[&amp;#34;落得下去&amp;#34;]
    THINK --&amp;gt; DO
    SAY --&amp;gt; DO
    SOLVE --&amp;gt; DO
    style E fill:#e8f4e8&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;思考与感悟之一&lt;/strong&gt;：这本书出版二十多年，具体技术在书中已经老（CVS、CORBA），纪律一条没过时。这印证了训练手册里那句判断——框架三个月过期，思维能力跟你三十年。&lt;strong&gt;读老书读「不变的部分」，是技术人对抗知识折旧的最优策略。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;思考与感悟之二&lt;/strong&gt;：全书的隐藏主线是「责任感」——对自己的代码负责（不妥协于巧合式编程）、对后来者负责（DRY、正交）、对用户负责（挖掘真需求）。所谓「修炼」，练的不是绝技，是&lt;strong&gt;把每一条朴素的纪律在 deadline 压力下依然执行到位&lt;/strong&gt;。知道和做到之间，隔着的不是天赋，是一百次「这次就算了」的拒绝。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;思考与感悟之三（系列收尾）&lt;/strong&gt;：五本书读下来，它们的交集惊人地小，合力惊人地大：学会提问管「别信错」，金字塔管「别说不清」，波利亚管「别卡住」，系统之美管「别复发」，修炼之道管「别妥协」。逻辑思维不是一个器官，是一套工具箱——&lt;strong&gt;工具箱的意义不在于拥有，在于每个场景知道伸手拿哪一件。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;附落地清单贴工位版--全书精选&#34;&gt;附：落地清单（贴工位版 · 全书精选）&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; 这个知识在系统里有几处表示？（DRY）&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; 代码能跑，但我说得清为什么吗？（拒绝巧合式编程）&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; 这次改动的爆炸半径到哪？（正交性）&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; 「不可能发生」的假设有断言守着吗？（契约）&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; 资源的打开和关闭在同一视野内吗？（有始有终）&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; 新项目第一周打通曳光弹了吗？（端到端骨架）&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; 原型代码被当成产品上线了吗？（用后即弃）&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; 今天离开代码时，比来时干净一点了吗？（童子军军规）&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    <item>
      <title>Google SRE 工作手册详细总结：写给从未接触过 SRE 的人</title>
      <link>https://ftmao.com/blog/bookstore/google-sre-workbook-summary/</link>
      <pubDate>Tue, 26 Oct 2021 10:00:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/bookstore/google-sre-workbook-summary/</guid>
      <description>&lt;h1 id=&#34;google-sre-工作手册详细总结写给从未接触过-sre-的人&#34;&gt;Google SRE 工作手册详细总结：写给从未接触过 SRE 的人&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;参考书籍：Google SRE 团队《The Site Reliability Workbook》（《SRE 工作手册》，2018）
目标读者：第一次听说 &amp;ldquo;SRE&amp;rdquo; 这个词，想知道它到底是什么、解决什么问题、能不能用在自己团队的人。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;这本书是什么&#34;&gt;〇、这本书是什么？&lt;/h2&gt;
&lt;p&gt;Google 在 2016 年出版了《Site Reliability Engineering》（第一本 SRE 书），讲的是 &lt;strong&gt;SRE 的原理&lt;/strong&gt;——Google 是怎么想的。2018 年这本《Workbook》（工作手册）是它的姊妹篇，讲的是 &lt;strong&gt;怎么落地&lt;/strong&gt;——两本书的章节基本一一对应（比如第一本书第 4 章&amp;quot;服务级别目标&amp;quot;对应本书第 2 章&amp;quot;实施 SLO&amp;quot;）。&lt;/p&gt;
&lt;p&gt;这本书最有价值的一点是：它故意收录了大量 &lt;strong&gt;Google 之外的公司&lt;/strong&gt; 的落地案例——Evernote、Home Depot（家得宝）、《纽约时报》、Spotify、Waze、PagerDuty——就是为了破除&amp;quot;SRE 只在 Google 那种规模和文化下才玩得转&amp;quot;的误解。全书由 100 多人贡献完成。&lt;/p&gt;
&lt;p&gt;用前言里 Mark Burgess 的话说，整本书可以浓缩成三句话：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;承诺：明确地设定服务的目标、期望和水平；&lt;/li&gt;
&lt;li&gt;评估：根据指标和预算约束持续评估这些承诺；&lt;/li&gt;
&lt;li&gt;反应：快速做出反应以兑现承诺。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;一sre-到底是什么和-devops-什么关系&#34;&gt;一、SRE 到底是什么？和 DevOps 什么关系？&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;SRE = Site Reliability Engineering，网站可靠性工程&lt;/strong&gt;。这个词由 Google 工程副总裁 Ben Treynor Sloss 创造。一句话概括：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;SRE 就是让软件工程师去做运维团队的工作&lt;/strong&gt;——&amp;ldquo;运维是一个软件问题&amp;rdquo;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;传统模式里，开发团队（追求快速出新功能）和运维团队（追求稳定别出事）天然对立：开发想&amp;quot;快发&amp;quot;，运维想&amp;quot;别动&amp;quot;。SRE 用一套&lt;strong&gt;量化的工程方法&lt;/strong&gt;消解这个矛盾，而不是靠开会吵架。&lt;/p&gt;
&lt;p&gt;那它和 DevOps 是什么关系？第 1 章的标题本身就是一行代码，堪称全书最著名的梗：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;SRE&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;implements&lt;/span&gt; DevOps&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;DevOps 是一套文化和哲学，SRE 是它的一种具体实现&lt;/strong&gt;——就像极限编程（XP）是敏捷（Agile）的一种具体实现。Puppet 前 VP Andrew Clay Shafer 在前言里甚至说：如果第一本 SRE 书早几年出版，社区会团结在&amp;quot;消除琐事&amp;quot;的旗帜下，&amp;ldquo;DevOps&amp;quot;这个词可能根本不会存在。&lt;/p&gt;
&lt;p&gt;DevOps 的五个关键思想与 SRE 的对应实现：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;DevOps 思想&lt;/th&gt;
					&lt;th&gt;SRE 的具体做法&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;不再有孤岛&lt;/td&gt;
					&lt;td&gt;SRE 与开发共享所有权、用相同的工具&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;事故是正常的，指责无用&lt;/td&gt;
					&lt;td&gt;对事不对人的事后总结（blameless postmortem）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;变更应该小步快跑&lt;/td&gt;
					&lt;td&gt;金丝雀发布、CI/CD、降低失败成本（MTTR）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;工具和文化相互关联&lt;/td&gt;
					&lt;td&gt;统一的工具链本身就是文化载体&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;度量至关重要&lt;/td&gt;
					&lt;td&gt;SLI / SLO / 错误预算&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;SRE 的七条核心原则：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;运维是一个软件问题&lt;/strong&gt;——用写代码的方式消灭重复劳动；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;按 SLO 管理&lt;/strong&gt;——不追求 100% 可用性（为什么，下面细讲）；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;努力减少琐事（toil）&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;自动化今年的工作&lt;/strong&gt;——Google 硬性规定 SRE 花在琐事上的时间不超过 50%，而且要把它当成&amp;quot;保证&amp;quot;而不是&amp;quot;上限&amp;rdquo;；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;通过降低失败成本来加快开发速度&lt;/strong&gt;——恢复快（MTTR 低）比不出事更重要；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;与开发者共享所有权&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;无论头衔如何，都使用相同的工具&lt;/strong&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这里有一个值得记住的反面案例：2017 年 5 月英国航空大停运，被书中引为&amp;quot;把运维当成本中心拼命压缩&amp;quot;的失败典型——一场大事故抵消了低成本运营省下的所有钱。&lt;/p&gt;
&lt;h2 id=&#34;二全书的地基slislosla-和错误预算&#34;&gt;二、全书的地基：SLI、SLO、SLA 和错误预算&lt;/h2&gt;
&lt;p&gt;第 2 章被作者称为&amp;quot;本书最重要的章节&amp;quot;，金句是：&lt;strong&gt;&amp;ldquo;没有 SLO，就不需要 SRE&amp;rdquo;&lt;/strong&gt;。这一节是全书的地基，务必看懂。&lt;/p&gt;
&lt;h3 id=&#34;21-三个容易混淆的概念&#34;&gt;2.1 三个容易混淆的概念&lt;/h3&gt;
&lt;p&gt;用一个&amp;quot;外卖小哥&amp;quot;的比喻：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SLI（Service Level Indicator，服务级别指标）&lt;/strong&gt;：你实际测出来的数字。比如&amp;quot;过去 30 天，99.2% 的外卖在 40 分钟内送达&amp;quot;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SLO（Service Level Objective，服务级别目标）&lt;/strong&gt;：你给自己定的目标。比如&amp;quot;我们承诺 99% 的订单 40 分钟内送达&amp;quot;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SLA（Service Level Agreement，服务级别协议）&lt;/strong&gt;：和客户签的、&lt;strong&gt;违约要赔钱&lt;/strong&gt;的商业合同。通常比 SLO 宽松，SLO 是内部&amp;quot;提前预警线&amp;quot;，SLA 是&amp;quot;最后底线&amp;quot;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;工程上的标准写法：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;SLI = 好事件数 / 总事件数        （0% ~ 100% 之间）
错误预算 = 100% − SLO&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;错误预算（Error Budget）&lt;/strong&gt; 是全书最重要的发明。它的逻辑是：既然 100% 不可靠也不必要，那么&amp;quot;允许出错的额度&amp;quot;就是一种&lt;strong&gt;可以花掉的预算&lt;/strong&gt;——你可以用它来快速发布新功能、做架构实验；预算花光了，就冻结发布，全员回头修可靠性。开发和运维的矛盾，从&amp;quot;立场之争&amp;quot;变成了&amp;quot;看数据说话&amp;quot;。&lt;/p&gt;
&lt;h3 id=&#34;22-为什么不追求-100-可用性&#34;&gt;2.2 为什么不追求 100% 可用性？&lt;/h3&gt;
&lt;p&gt;书中给了四个理由：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;组件同时故障的概率永远不为零，100% 在数学上就做不到；&lt;/li&gt;
&lt;li&gt;用户访问你的服务要经过很长的链路（手机 → 运营商 → 公网 → 机房），你把可用性从 99.9% 提到 99.99%，用户的实际体验提升微乎其微——边际效用递减；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;100% 意味着你不能做任何变更&lt;/strong&gt;，而变更是功能迭代的前提；&lt;/li&gt;
&lt;li&gt;100% 的 SLO 只是&amp;quot;运维团队的 SLO&amp;quot;，不是一种工程文化。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;还有一个很直观的乘法效应：如果你的服务依赖 9 个可用性 99.99% 的关键上游，你的总可用性 ≈ 0.9999¹⁰ ≈ &lt;strong&gt;99.9%&lt;/strong&gt;——4 个 9 直接掉到 3 个 9。第 19 章还有一个更扎心的数字：客户在 99.999% 的平台上自己只做到 99%，最终用户感受到的是 99% × 99.999% = &lt;strong&gt;98.99901%&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id=&#34;23-一个完整的例子手机游戏后端&#34;&gt;2.3 一个完整的例子：手机游戏后端&lt;/h3&gt;
&lt;p&gt;第 2 章用一个手机游戏案例贯穿始终，非常适合入门者理解。架构如下：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    A[&amp;#34;📱 手机 App&amp;#34;] --&amp;gt; B[&amp;#34;HTTP API 服务&amp;#34;]
    B --&amp;gt; C[(&amp;#34;永久存储&amp;lt;br/&amp;gt;游戏状态/排行榜&amp;#34;)]
    A --&amp;gt; D[(&amp;#34;用户数据存储&amp;lt;br/&amp;gt;头像等&amp;#34;)]
    C --&amp;gt; E[&amp;#34;⏰ 定时管道&amp;lt;br/&amp;gt;生成今日/本周/历史排行榜&amp;#34;]
    E --&amp;gt; F[(&amp;#34;排行榜数据存储&amp;#34;)]
    G[&amp;#34;🌐 网站&amp;#34;] --&amp;gt; F&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;先量现状：过去 4 周 API 总请求 3,663,253 次，成功 3,557,865 次（可用性 97.123%），P90 延迟 432ms，P99 延迟 891ms。据此定出&lt;strong&gt;入门级 SLO&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;可用性：97% 请求成功&lt;/li&gt;
&lt;li&gt;延迟：90% 请求 &amp;lt; 450ms，99% 请求 &amp;lt; 900ms&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;对应的错误预算：每四周允许失败约 109,897 个请求。&lt;/p&gt;
&lt;p&gt;然后看两个真实事故的&amp;quot;预算消耗&amp;quot;对比，这是全书最有说服力的算账方式：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;事故&lt;/th&gt;
					&lt;th&gt;错误数&lt;/th&gt;
					&lt;th&gt;消耗预算&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;一次坏发布，NullPointerException 持续 4 小时&lt;/td&gt;
					&lt;td&gt;14,066&lt;/td&gt;
					&lt;td&gt;13%&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;唯一的状态数据库故障，从备份恢复花了 20 小时&lt;/td&gt;
					&lt;td&gt;~72,000&lt;/td&gt;
					&lt;td&gt;65%&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;结论很反直觉：&lt;strong&gt;五年只发生一次的数据库故障，远没有每年 2~3 次的坏发布值得投入&lt;/strong&gt;——改进发布流程的优先级应该高于调查服务器故障。这就是错误预算的意义：它把&amp;quot;该把工程资源投到哪&amp;quot;从拍脑袋变成了算术题。&lt;/p&gt;
&lt;h3 id=&#34;24-落地-slo-的完整流程&#34;&gt;2.4 落地 SLO 的完整流程&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    A[&amp;#34;绘制服务架构图&amp;#34;] --&amp;gt; B[&amp;#34;组件分类：&amp;lt;br/&amp;gt;请求驱动 / 管道 / 存储&amp;#34;]
    B --&amp;gt; C[&amp;#34;为每类组件选 SLI 规范&amp;lt;br/&amp;gt;（不超过 5 种）&amp;#34;]
    C --&amp;gt; D[&amp;#34;选择 SLI 实现方式&amp;lt;br/&amp;gt;（日志 → 探针 → 客户端插桩，精度递增）&amp;#34;]
    D --&amp;gt; E[&amp;#34;基于历史数据定初始 SLO&amp;#34;]
    E --&amp;gt; F{&amp;#34;三方达成一致？&amp;lt;br/&amp;gt;产品：对用户够用&amp;lt;br/&amp;gt;开发：预算耗尽愿意行动&amp;lt;br/&amp;gt;SRE：不靠英雄主义守得住&amp;#34;}
    F -- 否 --&amp;gt; E
    F -- 是 --&amp;gt; G[&amp;#34;白纸黑字写错误预算政策&amp;lt;br/&amp;gt;（预算耗尽怎么办）&amp;#34;]
    G --&amp;gt; H[&amp;#34;监控 + 告警上线&amp;#34;]
    H --&amp;gt; I[&amp;#34;定期审查迭代 SLO&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;三类组件各自推荐的 SLI：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;请求驱动型&lt;/strong&gt;（API 服务）：可用性、延迟、品质；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;管道/流水线型&lt;/strong&gt;（数据处理）：新鲜度、正确性、覆盖范围；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;存储型&lt;/strong&gt;：耐用性（数据丢不丢）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;错误预算政策里要事先写清楚&amp;quot;预算耗尽怎么办&amp;quot;：可靠性 bug 最优先、冻结生产变更（P0 问题和安全补丁除外），极端时宣布&amp;quot;紧急状态&amp;quot;，直到服务回到 SLO 以内。附录 B 还有一个值得抄的细节：&lt;strong&gt;单个事件消耗超过四周预算的 20% → 必须做事后调查，且至少包含一个 P0 行动项；对错误预算计算有分歧 → 上报 CTO 裁决&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id=&#34;三非-google-公司怎么落地两个完整案例&#34;&gt;三、非 Google 公司怎么落地？两个完整案例&lt;/h2&gt;
&lt;h3 id=&#34;31-evernote和云厂商打破-slo-的墙&#34;&gt;3.1 Evernote：和云厂商&amp;quot;打破 SLO 的墙&amp;quot;&lt;/h3&gt;
&lt;p&gt;背景：全球 2.2 亿用户、存储 120 亿条信息、750+ 个 MySQL 实例，从自建数据中心迁往 GCP。传统&amp;quot;开发 vs 运维&amp;quot;两种模式（&amp;ldquo;you build it, you run it&amp;rdquo; 和 &amp;ldquo;you build it, we run it&amp;rdquo;）都试过了，都不满意，于是在 Google CRE（客户可靠性工程）团队帮助下落地 SLO。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;首版 SLO：按月窗口、99.95% 正常运行时间；用第三方探针 Pingdom 每分钟轮询，需两个地理独立的探针连续失败才算&amp;quot;停机&amp;quot;；&lt;strong&gt;维护窗口也算停机&lt;/strong&gt;（用户不知情就是不可用）；&lt;/li&gt;
&lt;li&gt;治理节奏：每月与 Google 团队审查一次 SLO，每 6 个月修订一次，9 个月迭代了 3 版；指导原则是&amp;quot;&lt;strong&gt;完美是良好的敌人&lt;/strong&gt;&amp;quot;——先有再优；&lt;/li&gt;
&lt;li&gt;一个漂亮的故事：2017 年讨论是否压缩发布窗口时，双方用 SLO 计算把发布窗口从 5 个减到 2 个，&lt;strong&gt;量化了客户影响，消除了主观争论&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;最创新的一点：与 Google 共享实时 SLO 仪表板。于是 Google 的告警从泛泛的&amp;quot;GCP 负载均衡今天有点慢&amp;quot;变成&amp;quot;该问题影响 Evernote SLO 的 5%&amp;quot;，重大事件双方作为联合 P1 故障单处理。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;32-home-depot家得宝传统企业的规模化推广&#34;&gt;3.2 Home Depot（家得宝）：传统企业的规模化推广&lt;/h3&gt;
&lt;p&gt;背景：北美 2,200+ 门店、近 40 万员工、每年 15 亿+ 交易——证明 SRE 不是互联网公司专利。&lt;/p&gt;
&lt;p&gt;他们发明了 &lt;strong&gt;VALET 框架&lt;/strong&gt;（呼应 Google 的四大黄金信号，连批处理系统都能套用）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;V&lt;/strong&gt;olume 容量（流量/处理记录数）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A&lt;/strong&gt;vailability 可用性&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;L&lt;/strong&gt;atency 延迟&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;E&lt;/strong&gt;rrors 错误&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;T&lt;/strong&gt;ickets 故障单（人工介入次数）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;推广靠四板斧：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    subgraph 推广四支柱
        A[&amp;#34;通用语言&amp;lt;br/&amp;gt;VALET 缩写，人人会说&amp;#34;]
        B[&amp;#34;布道&amp;lt;br/&amp;gt;内部博客/路演/T恤贴纸&amp;lt;br/&amp;gt;FiRE 学院培训&amp;#34;]
        C[&amp;#34;自动化&amp;lt;br/&amp;gt;TPS Report 自动采集指标&amp;lt;br/&amp;gt;VALET 仪表板 + 聊天机器人&amp;#34;]
        D[&amp;#34;激励&amp;lt;br/&amp;gt;SLO 纳入开发经理年度目标&amp;#34;]
    end
    A &amp;amp; B &amp;amp; C &amp;amp; D --&amp;gt; E[&amp;#34;一年从 0 到 800 个服务&amp;lt;br/&amp;gt;每月新增约 50 个&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;给产品经理的简化分级表也很实用：99.5% = MVP/内部工具；99.9% = 大多数非销售系统；99.95% = 销售系统；99.99% = 共享基础设施。他们的原话值得引用：&amp;ldquo;我们是一家传统企业；如果我们能成功引入这么大的变化，您也可以。&amp;rdquo;&lt;/p&gt;
&lt;h2 id=&#34;四监控与告警在预算烧光之前叫醒你&#34;&gt;四、监控与告警：在预算烧光之前叫醒你&lt;/h2&gt;
&lt;h3 id=&#34;41-监控的基本盘&#34;&gt;4.1 监控的基本盘&lt;/h3&gt;
&lt;p&gt;监控的五大用途：告警、诊断、可视化、长期趋势/容量规划、变更前后对比。&lt;strong&gt;指标和日志要分工&lt;/strong&gt;：指标近实时但粒度低（告警、仪表板用它），日志有延迟但精确（根因分析用它）。数据新鲜度很关键——超过 4~5 分钟的失效数据会严重影响事件响应。&lt;/p&gt;
&lt;p&gt;现代监控栈是松散耦合的：Prometheus（采集/规则）→ InfluxDB（长期存储）→ Alertmanager（告警聚合）→ Grafana（仪表板）。&lt;/p&gt;
&lt;h3 id=&#34;42-告警的正确姿势盯着错误预算的消耗率&#34;&gt;4.2 告警的正确姿势：盯着错误预算的&amp;quot;消耗率&amp;quot;&lt;/h3&gt;
&lt;p&gt;新手最常见的告警写法是&amp;quot;错误率超过 X% 就报警&amp;quot;，书第 5 章用一整章论证这有多糟糕，并给出了演进路径：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    M1[&amp;#34;方法1：错误率≥SLO 就告警&amp;lt;br/&amp;gt;❌ 太敏感：10 分钟 0.1% 错误率就告警&amp;lt;br/&amp;gt;实际只消耗月预算 0.02%，每天可收 144 条&amp;#34;]
    M2[&amp;#34;方法2：加大告警窗口&amp;lt;br/&amp;gt;❌ 检测太慢，大事故发现太晚&amp;#34;]
    M3[&amp;#34;方法3：加持续时间参数 for:1h&amp;lt;br/&amp;gt;❌ 每 10 分钟一次 5 分钟的 100% 错误尖峰&amp;lt;br/&amp;gt;累计烧掉 35% 预算却永不触发&amp;#34;]
    M4[&amp;#34;方法4：按消耗率 burn rate 告警&amp;lt;br/&amp;gt;⚠️ 进步明显，但重置时间长&amp;#34;]
    M5[&amp;#34;方法5：多消耗率分档&amp;lt;br/&amp;gt;⚠️ 快烧/慢烧分开告警&amp;#34;]
    M6[&amp;#34;方法6：多窗口多消耗率 ✅&amp;lt;br/&amp;gt;长窗保证精确度，短窗保证灵敏度&amp;#34;]
    M1 --&amp;gt; M2 --&amp;gt; M3 --&amp;gt; M4 --&amp;gt; M5 --&amp;gt; M6&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;消耗率（burn rate）&lt;/strong&gt; = 当前错误率相对 SLO 允许错误率的倍数。对照表（99.9% SLO，30 天窗口）：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;消耗率&lt;/th&gt;
					&lt;th&gt;错误率&lt;/th&gt;
					&lt;th&gt;耗尽预算需时&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;1&lt;/td&gt;
					&lt;td&gt;0.1%&lt;/td&gt;
					&lt;td&gt;30 天&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;10&lt;/td&gt;
					&lt;td&gt;1%&lt;/td&gt;
					&lt;td&gt;3 天&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;1000&lt;/td&gt;
					&lt;td&gt;100%&lt;/td&gt;
					&lt;td&gt;43 分钟&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;最终推荐的&amp;quot;多窗口多消耗率&amp;quot;参数（针对 99.9% SLO，可直接抄）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;呼叫值班（紧急）&lt;/strong&gt;：1 小时长窗 + 5 分钟短窗，消耗率 14.4（约 2% 预算/小时）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;呼叫值班（次紧急）&lt;/strong&gt;：6 小时 + 30 分钟，消耗率 6&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;工单（不紧急）&lt;/strong&gt;：3 天 + 6 小时，消耗率 1&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;短窗口取长窗口的 1/12。两个极端情况的提醒：低流量服务（每小时 10 个请求时，1 个失败就是 10% 错误率）要合并请求或拉长时间窗；99.999% 的服务 100% 中断 &lt;strong&gt;26 秒&lt;/strong&gt;就耗尽预算——比大多数监控采集间隔还短，只能靠自愈。&lt;/p&gt;
&lt;h2 id=&#34;五消除琐事toilsre-的时间去哪了&#34;&gt;五、消除琐事（Toil）：SRE 的时间去哪了&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;琐事（toil）&lt;/strong&gt; 的六大特征：手动、重复、可自动化、被动应对、无持久价值、随服务规模线性增长。Google 规定 SRE 花在琐事上的时间不超过 50%，保证至少一半时间做&amp;quot;让明天比今天更好&amp;quot;的工程。&lt;/p&gt;
&lt;p&gt;书中几个精彩案例：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;John Looney 修内核&lt;/strong&gt;：2005 年他入职 Google 时的活儿是修坏机器——任何时候都有超过 20,000 台坏机器。发现大量机器是日志把根分区填满，他本想写个脚本&amp;quot;截断大日志 + 重启 syslog&amp;quot;了事，被队友拦下要求修根因。最终他向 Linux 内核提交补丁，并用&amp;quot;这个问题导致 Google 每小时损失约 1000 美元&amp;quot;的成本分析说服了内核团队。&lt;strong&gt;修症状是琐事，修根因才是工程。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&amp;ldquo;不完美的自动化就够了&amp;rdquo;&lt;/strong&gt;：Google 数据中心网络维修自动化（Saturn/Jupiter 项目）中，内存错误诊断被过度分析了三年（收集了 650+ 个离散问题数据），后来发现多数错误是暂时性的，重启重装即可恢复——完美的自动化不值得，&amp;ldquo;足够好&amp;quot;的自动化先跑起来。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&amp;ldquo;买下那个侏儒&amp;rdquo;&lt;/strong&gt;：早年 Froogle 搜索&amp;quot;跑鞋&amp;quot;会返回一个穿跑鞋的花园侏儒（eBay 商品），修不掉这个 bug，团队干脆在 eBay 上把侏儒买了下来。引申义：&lt;strong&gt;有时改变现实去适应代码，比反过来便宜。&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;遗留系统的四阶段处理路径：&lt;strong&gt;避免 → 封装/增强（高息技术债转低息）→ 替换/重构 → 退役/监护&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;与琐事相关的第 7 章&amp;quot;简单化&amp;quot;还有两个名案例：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Borg vs Omega&lt;/strong&gt;：Omega 本想做成&amp;quot;更干净的 Borg&amp;quot;重写版，但 Borg 持续演进让 Omega 永远追不上移动的目标，迁移成本高到不可行。结局：Omega 的想法回流 Borg，概念最终催生了开源的 &lt;strong&gt;Kubernetes&lt;/strong&gt;。教训：重写前先算一算&amp;quot;同样的精力投入改进现有系统会怎样&amp;rdquo;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;pDNS 循环依赖&lt;/strong&gt;：客户端查服务 IP 依赖 Svelte，Svelte 依赖 pDNS，pDNS 的负载均衡器又依赖 Svelte——&amp;ldquo;我们就像洞穴居民，只能用上一次篝火点燃的火炬点火&amp;rdquo;（系统冷启动成为不可能）。解法：低级组件本地缓存关键 IP 列表，打破环路。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;六值班与事件响应像消防队一样打仗&#34;&gt;六、值班与事件响应：像消防队一样打仗&lt;/h2&gt;
&lt;h3 id=&#34;61-可持续的值班体系&#34;&gt;6.1 可持续的值班体系&lt;/h3&gt;
&lt;p&gt;关键数字：轮班不超过 12 小时；&lt;strong&gt;每 12 小时班次最多 2 个事件&lt;/strong&gt;（再多就处理不过来了）；多站点 24/7 值班每站点至少 5 人；保证 ≥50% 的时间留给项目工作。警惕&amp;quot;温水煮青蛙&amp;quot;式的运维过载——值班负载用 21 天移动平均来看趋势。&lt;/p&gt;
&lt;p&gt;Evernote 的事件分级很清爽：&lt;strong&gt;P1 立即处理&lt;/strong&gt;（呼叫值班、影响 SLO）、&lt;strong&gt;P2 下一工作日&lt;/strong&gt;（邮件通知）、&lt;strong&gt;P3 仅供参考&lt;/strong&gt;（只看仪表板）。&lt;/p&gt;
&lt;h3 id=&#34;62-事件指挥体系icsimag&#34;&gt;6.2 事件指挥体系（ICS/IMAG）&lt;/h3&gt;
&lt;p&gt;大规模事件的打法直接借用了消防系统：1968 年消防员为应对野火创建的&lt;strong&gt;事件指挥系统（ICS）&lt;/strong&gt;，Google 内部叫 IMAG。核心思想：混乱中最大的敌人不是故障本身，而是&amp;quot;谁说了算都不知道&amp;quot;。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TD
    IC[&amp;#34;🎖️ 事件指挥官 IC&amp;lt;br/&amp;gt;总负责，做决策，默认承担未委派的角色&amp;#34;]
    CL[&amp;#34;📢 沟通主管 CL&amp;lt;br/&amp;gt;对用户/领导/媒体发通告&amp;#34;]
    OL[&amp;#34;🔧 运维主管 OL&amp;lt;br/&amp;gt;带领团队执行技术操作&amp;#34;]
    IC --&amp;gt; CL
    IC --&amp;gt; OL&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;事件管理围绕&amp;quot;三个 C&amp;quot;：&lt;strong&gt;Coordinate（协调）、Communicate（沟通）、Control（控制）&lt;/strong&gt;。四条基本原则：保持命令清晰、明确角色、实时记录、&lt;strong&gt;尽早且频繁地宣布事件&lt;/strong&gt;。行动顺序永远是&amp;quot;缓解优先&amp;quot;：评估影响 → 缓解影响 → 根因分析 → 修复与事后报告。&lt;/p&gt;
&lt;p&gt;三个真实案例，一正一反一个惊险：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;反例：Google Home/Assistant 事件（2017）&lt;/strong&gt;：一个 bug 让文件获取频率比预期高 50 倍，5 月 22 日就发现 QPS 异常，但误判了根因，而且&lt;strong&gt;在周六把灰度推到了 100%&lt;/strong&gt;（违反&amp;quot;首次上线仅在工作日&amp;quot;的铁律），用户丢了一半请求，靠开发者周末&amp;quot;英雄主义&amp;quot;硬扛。教训：没有尽早宣布事件、缓解后没定根因就继续推送。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;惊险：GKE CreateCluster 中断&lt;/strong&gt;：欧洲集群创建失败率超 60%，根因竟是从 DockerHub 拉取的一个镜像损坏（美国正常）。伦敦 SRE 6:41 被呼叫，9:10 才正式建立 IMAG 结构（太晚了），最后发现用现成的 GCR 镜像站点（&lt;code&gt;--registry-mirror=https://mirror.gcr.io&lt;/code&gt;）就能缓解——&lt;strong&gt;镜像其实早就就位了&lt;/strong&gt;。总中断 6 小时 40 分钟，41 人参与，事后报告列出 28 个行动项。最大教训：发现大致位置后就该先用&amp;quot;通用缓解&amp;quot;（回滚到已知良好状态），而不是继续深挖根因。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;正面：比利时数据中心雷击（2015）&lt;/strong&gt;：两分钟内 4 次雷击，UPS 电池在第 3、4 次雷击间隔太近时未能切换，磁盘托盘断电。事件指挥得当：IC 定了三个明确目标，沟通主管对四类受众（公司领导、依赖团队、外部客户、工单客户）分别通报。最终数据丢失仅 &lt;strong&gt;0.000001%&lt;/strong&gt;——管理良好的事件，损失可以极小。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;七事后总结文化从失败中学习&#34;&gt;七、事后总结文化：从失败中学习&lt;/h2&gt;
&lt;p&gt;SRE 文化的灵魂是&lt;strong&gt;对事不对人的事后总结（blameless postmortem）&lt;/strong&gt;。第 10 章用同一个真实事故的&amp;quot;坏版本&amp;quot;和&amp;quot;好版本&amp;quot;两版报告做对比，极其有教育意义。&lt;/p&gt;
&lt;p&gt;事故本身（2014-08-11，Google 卫星擦除事故）：Google 在托管机房部署的代理/缓存机架叫&amp;quot;卫星&amp;quot;。一次机架退役流程中，自动化中途失败，工程师手动重跑——重跑时 &lt;code&gt;GetMachines()&lt;/code&gt; 返回了空列表，而 API 有个 bug：&lt;strong&gt;空列表被当作&amp;quot;无过滤条件&amp;quot;，而不是&amp;quot;不操作任何机器&amp;quot;&lt;/strong&gt;。于是全球所有卫星机器在几分钟内被磁盘擦除。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    A[&amp;#34;机架退役自动化失败&amp;#34;] --&amp;gt; B[&amp;#34;工程师手动重跑流程&amp;#34;]
    B --&amp;gt; C[&amp;#34;GetMachines() 返回空列表&amp;#34;]
    C --&amp;gt; D[&amp;#34;API bug：空列表 = 无过滤条件&amp;lt;br/&amp;gt;（而不是不操作任何机器）&amp;#34;]
    D --&amp;gt; E[&amp;#34;全球所有卫星进入退役流程&amp;#34;]
    E --&amp;gt; F[&amp;#34;磁盘被全部擦除 💥&amp;#34;]
    F --&amp;gt; G[&amp;#34;边缘流量切到核心集群&amp;lt;br/&amp;gt;用户延迟上升约两天&amp;#34;]
    G -.-&amp;gt;|&amp;#34;容量规划良好，&amp;lt;br/&amp;gt;用户几乎无感知&amp;#34;| H[&amp;#34;几天后恢复&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;两版事后报告的对比：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;&lt;/th&gt;
					&lt;th&gt;坏版本&lt;/th&gt;
					&lt;th&gt;好版本&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;发布时间&lt;/td&gt;
					&lt;td&gt;拖了 4 个月&lt;/td&gt;
					&lt;td&gt;4 天内（8/11 出事，8/15 发布）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;根本原因&lt;/td&gt;
					&lt;td&gt;&amp;ldquo;dylanfour@ 粗心大意忽略自动化&amp;rdquo;&lt;/td&gt;
					&lt;td&gt;深入分析 API 语义缺陷与流程漏洞&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;行动项&lt;/td&gt;
					&lt;td&gt;全是 P2，措辞含糊（&amp;ldquo;使自动化更好&amp;rdquo;）&lt;/td&gt;
					&lt;td&gt;每项有唯一所有者、优先级、bug 号，含 P0 级&amp;quot;大红色按钮&amp;quot;（紧急停止开关）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;分享范围&lt;/td&gt;
					&lt;td&gt;团队小范围&lt;/td&gt;
					&lt;td&gt;全公司工程师&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;后续更有说服力：三年后类似事件重演，但第一次事后总结落实的行动项大幅缩小了爆炸半径。Google 运维副总裁 Ben Treynor Sloss 的原话：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;没有后续行动的事后总结等于没写事后总结。所有影响用户的停机事后总结必须至少关联一个 P0/P1 bug，例外由我亲自审批，例外极少。&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;事后总结文化崩塌的信号也要警惕：工程师回避参与、高管说指责性语言、没时间写、同类事故重复发生。&lt;/p&gt;
&lt;h2 id=&#34;八管理负载pokémon-go-的惊险一课&#34;&gt;八、管理负载：Pokémon GO 的惊险一课&lt;/h2&gt;
&lt;p&gt;Google 管理流量的三层工具：&lt;strong&gt;负载均衡 + 自动扩缩容 + 减载（load shedding）&lt;/strong&gt;。核心警告是：&lt;strong&gt;必须把它们当作一个整体系统设计，否则工具之间会形成灾难性的反馈回路。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&#34;81-案例pokémon-go2016真实&#34;&gt;8.1 案例：Pokémon GO（2016，真实）&lt;/h3&gt;
&lt;p&gt;上线前按最乐观估计的 5 倍做了负载测试，实际流量是估计的近 &lt;strong&gt;50 倍&lt;/strong&gt;。随后发生的级联故障堪称教科书：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    A[&amp;#34;真实流量超预期 50 倍&amp;#34;] --&amp;gt; B[&amp;#34;Niantic 前端代理跟不上入站连接&amp;#34;]
    B --&amp;gt; C[&amp;#34;后端变慢但不拒绝请求&amp;#34;]
    C --&amp;gt; D[&amp;#34;负载均衡层自动重试 GET&amp;#34;]
    D --&amp;gt; E[&amp;#34;GFE 的 SSL 客户端代码被打到极限&amp;#34;]
    E --&amp;gt; F[&amp;#34;GCLB 全球容量实际下降 50% ⚠️&amp;#34;]
    C --&amp;gt; G[&amp;#34;客户端立即重试 + 固定退避&amp;lt;br/&amp;gt;错误时所有客户端同步重试（雷群）&amp;#34;]
    G --&amp;gt; H[&amp;#34;同步峰值达此前全球 RPS 峰值的 20 倍&amp;#34;]
    F &amp;amp; H --&amp;gt; I[&amp;#34;Google SRE 三步止血：&amp;lt;br/&amp;gt;①隔离 Pokémon GO 专用池 ②扩容 ③限流&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;事后双方各自改进：Niantic 给客户端加了&lt;strong&gt;抖动（jitter）+ 截断指数退避&lt;/strong&gt;；Google 学会把 GFE 后端当潜在负载源、建立准入负载测试。共同教训：&lt;strong&gt;尽可能靠近客户端测量负载&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id=&#34;82-案例dressy-减载反馈回路虚构但典型&#34;&gt;8.2 案例：Dressy 减载反馈回路（虚构但典型）&lt;/h3&gt;
&lt;p&gt;三区域部署，A 区流量上涨先触发减载（CPU 超阈值直接拒绝请求）→ 被拒请求几乎不耗 CPU → 负载均衡器一看&amp;quot;A 区每个请求成本只有 B/C 的 1/10，效率最高！&amp;quot;→ 把更多流量发给 A → A 区 503 爆炸。恶性循环的根源：&lt;strong&gt;配减载的和配负载均衡的是两拨人，没人把它们当统一的负载管理系统检查&lt;/strong&gt;。修法很巧妙：让被拒绝的请求按 120% CPU 计入利用率，A 区立刻&amp;quot;显得&amp;quot;过载，流量自然流向 B/C。&lt;/p&gt;
&lt;h2 id=&#34;九nalsd把白板上的架构图翻译成数字&#34;&gt;九、NALSD：把白板上的架构图翻译成数字&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;NALSD（Non-Abstract Large System Design，非抽象大型系统设计）&lt;/strong&gt; 是 Google 要求的系统设计方法：从问题出发迭代设计，每一步都必须把架构图翻译成具体的资源估算（多少台机器、多少字节、多少 IOPS），并回答四个问题——&amp;ldquo;可能吗？能做得更好吗？可行吗（钱和硬件）？有弹性吗（故障怎么办）？&amp;rdquo;&lt;/p&gt;
&lt;p&gt;书中用 AdWords 点击率统计系统做了五轮完整迭代：每秒 50 万次搜索查询、1 万次广告点击，为数百万广告主出实时仪表板（SLO：99.9% 的查询 1 秒内完成，数据延迟不超过 5 分钟）。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    I1[&amp;#34;迭代1 单机方案&amp;lt;br/&amp;gt;100TB/天日志&amp;lt;br/&amp;gt;需 2500 块 HDD 或 1563 台机器&amp;lt;br/&amp;gt;❌ 单点故障一串&amp;#34;] --&amp;gt; I2[&amp;#34;迭代2 MapReduce 批处理&amp;lt;br/&amp;gt;❌ 做不到 5 分钟内可见&amp;#34;]
    I2 --&amp;gt; I3[&amp;#34;迭代3 LogJoiner&amp;lt;br/&amp;gt;流式 join 查询日志与点击日志&amp;lt;br/&amp;gt;⚠️ IOPS 单机扛不住&amp;#34;]
    I3 --&amp;gt; I4[&amp;#34;迭代4 分片 + 副本&amp;lt;br/&amp;gt;hash(query_id) mod N&amp;lt;br/&amp;gt;⚠️ 单数据中心仍是单点&amp;#34;]
    I4 --&amp;gt; I5[&amp;#34;迭代5 多数据中心 + Paxos ✅&amp;lt;br/&amp;gt;每 DC 约 4TB RAM、64 台机器&amp;lt;br/&amp;gt;网络带宽只用 25%&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;给面试和日常设计的启发：每轮迭代不是&amp;quot;画个更复杂的图&amp;quot;，而是&lt;strong&gt;算一笔账&lt;/strong&gt;——日数据量 = QPS × 86400 × 条目大小；磁盘数 = QPS ÷ 单盘 IOPS；Paxos 顺序吞吐 = 1000ms ÷ 跨机房 RTT。假设不必完美，有价值的是把多个不完美但合理的估算组合起来做决策。&lt;/p&gt;
&lt;h2 id=&#34;十数据处理管道配置与金丝雀发布&#34;&gt;十、数据处理管道、配置与金丝雀发布&lt;/h2&gt;
&lt;h3 id=&#34;101-把管道当服务运营spotify-案例&#34;&gt;10.1 把管道当服务运营（Spotify 案例）&lt;/h3&gt;
&lt;p&gt;数据管道也要定 SLO：新鲜度（&amp;ldquo;99% 的数据 30 分钟内处理完&amp;rdquo;）、正确性、完整性。Spotify 的事件传递系统每天收集&lt;strong&gt;数千亿条事件&lt;/strong&gt;（听歌、点广告……），用于向艺术家支付版税——可靠性是财务级要求。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    A[&amp;#34;Spotify 客户端事件&amp;#34;] --&amp;gt; B[&amp;#34;Cloud Pub/Sub 主题&amp;lt;br/&amp;gt;（按事件类型完全隔离）&amp;#34;]
    B --&amp;gt; C[&amp;#34;消费微服务&amp;#34;]
    C --&amp;gt; D[&amp;#34;分区微服务&amp;lt;br/&amp;gt;分配到小时桶&amp;#34;]
    D --&amp;gt; E[&amp;#34;Dataproc 批作业去重&amp;#34;]
    E --&amp;gt; F[(&amp;#34;GCS 小时桶&amp;lt;br/&amp;gt;与下游的唯一接口&amp;#34;)]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;三个值得抄的设计决策：时间戳用&lt;strong&gt;服务器接收时间&lt;/strong&gt;而非客户端时间（用户可以离线缓冲 30 天、可以改设备时间）；只给&amp;quot;财务相关&amp;quot;事件承诺偏度/完整性 SLO，&lt;strong&gt;刻意不提供数据质量 SLO&lt;/strong&gt;——&amp;ldquo;我们是邮政服务：负责按时完整送达，包裹里的内容质量归寄件人&amp;rdquo;；容量规划留安全边际，高峰 CPU 不超 50%。&lt;/p&gt;
&lt;h3 id=&#34;102-配置最好的人机界面是没有配置&#34;&gt;10.2 配置：最好的人机界面是&amp;quot;没有配置&amp;quot;&lt;/h3&gt;
&lt;p&gt;配置三要素哲学：理想配置是&lt;strong&gt;完全没有配置&lt;/strong&gt;（系统自动识别正确值）；以用户为中心（用户说&amp;quot;我要热的绿茶&amp;quot;，而不是指定水量/温度/浸泡时间）；动态默认值（线程数默认 = CPU 核数）。任何配置变更必须满足安全三属性：&lt;strong&gt;逐步部署、可回滚、失控时自动回滚&lt;/strong&gt;（想想多少人用防火墙规则把自己锁在服务器外面过）。&lt;/p&gt;
&lt;h3 id=&#34;103-金丝雀发布用小部分错误预算买安全感&#34;&gt;10.3 金丝雀发布：用小部分错误预算买安全感&lt;/h3&gt;
&lt;p&gt;术语来自煤矿里的金丝雀。核心是一笔账：一个缺陷导致 20% 请求出错，直接全量发布就是 20% 总错误率；先部署到 5% 的金丝雀集群，&lt;strong&gt;总错误率只有 5% × 20% = 1%&lt;/strong&gt;。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    A[&amp;#34;新版本部署到 5% 金丝雀集群&amp;#34;] --&amp;gt; B[&amp;#34;细分指标对比：&amp;lt;br/&amp;gt;金丝雀组 vs 对照组&amp;#34;]
    B --&amp;gt; C{&amp;#34;错误率/延迟显著偏离？&amp;#34;}
    C -- 是 --&amp;gt; D[&amp;#34;暂停发布 + 回滚&amp;lt;br/&amp;gt;通知人工介入&amp;#34;]
    C -- 否 --&amp;gt; E[&amp;#34;逐步扩大：1%→10%→50%→100%&amp;#34;]
    E --&amp;gt; F[&amp;#34;全量发布 ✅&amp;#34;]
    D -.-&amp;gt;|&amp;#34;修复后重来&amp;#34;| A&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;两个容易踩的坑：监控指标的聚合间隔必须&lt;strong&gt;小于等于金丝雀时长&lt;/strong&gt;（30 分钟的金丝雀用&amp;quot;每小时错误数&amp;quot;评估，噪声会淹没信号）；金丝雀指标首选 SLI，且&lt;strong&gt;总数不要超过 12 个&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id=&#34;十一人的部分过载恢复团队建设与组织变革&#34;&gt;十一、人的部分：过载恢复、团队建设与组织变革&lt;/h2&gt;
&lt;p&gt;第三部分&amp;quot;流程&amp;quot;处理的是书中最&amp;quot;软&amp;quot;也最难的部分。&lt;/p&gt;
&lt;h3 id=&#34;111-识别过载并恢复第-17-章&#34;&gt;11.1 识别过载并恢复（第 17 章）&lt;/h3&gt;
&lt;p&gt;过载分两种：&lt;strong&gt;真实过载&lt;/strong&gt;（任务量客观超支）和&lt;strong&gt;感知过载&lt;/strong&gt;（沟通不足导致的主观感受）——书中强调，感知过载的实际危害和真实过载一样大，而且会传染。恶性循环是这样的：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    A[&amp;#34;运维过载&amp;#34;] --&amp;gt; B[&amp;#34;心理压力&amp;#34;]
    B --&amp;gt; C[&amp;#34;犯错增多&amp;#34;]
    C --&amp;gt; D[&amp;#34;可靠性下降&amp;#34;]
    D --&amp;gt; E[&amp;#34;更多运维工作&amp;#34;]
    E --&amp;gt; A
    B --&amp;gt; F[&amp;#34;心理安全下降&amp;lt;br/&amp;gt;协作与信息共享停滞&amp;#34;]
    F --&amp;gt; G[&amp;#34;效率更低&amp;#34;]
    G --&amp;gt; E&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;真实案例：Google 一个支撑 Gmail/Drive 后端的存储 SRE 团队，2016 年&lt;strong&gt;三分之二的成员&lt;/strong&gt;短期相继转岗。恢复方法朴素得出奇：全队进一个房间把所有职责写上白板分类，两天时间（一天分类、一天写文档/做自动化）清理了几个月的积压；之后每两周分类一次，&lt;strong&gt;每人最多 10 张打开的工单&lt;/strong&gt;，超了就有五种干预动作。另一个感知过载团队（悉尼/苏黎世两地）的恢复路线是分三档：短期先减压（圆桌吐槽会、删掉垃圾警报、&amp;ldquo;慷慨地静音&amp;rdquo;）、中期减负（把一项服务交还开发团队、补员）、长期治本（SLO 对齐、服务统一化）。约一年后匿名调查显示团队重回&amp;quot;高效且安全&amp;quot;。&lt;/p&gt;
&lt;h3 id=&#34;112-sre-怎么参与一个服务第-18-章&#34;&gt;11.2 SRE 怎么参与一个服务（第 18 章）&lt;/h3&gt;
&lt;p&gt;SRE 永远是稀缺资源（Google 把 SRE:开发比例控制在 1:10 左右），所以要主动选择参与模式。服务的生命周期：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    A[&amp;#34;架构与设计&amp;#34;] --&amp;gt; B[&amp;#34;积极开发&amp;lt;br/&amp;gt;（生产化）&amp;#34;] --&amp;gt; C[&amp;#34;有限可用 Beta&amp;lt;br/&amp;gt;GA 前必须定 SLO&amp;#34;] --&amp;gt; D[&amp;#34;一般可用 GA&amp;lt;br/&amp;gt;通过 PRR 生产准备审查&amp;#34;] --&amp;gt; E[&amp;#34;弃用&amp;lt;br/&amp;gt;SRE 同时运维新旧两套&amp;#34;] --&amp;gt; F[&amp;#34;放弃&amp;lt;br/&amp;gt;运维交还开发团队&amp;#34;] --&amp;gt; G[&amp;#34;不支持&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;两个一正一反的收场案例：&lt;strong&gt;Ares 团队（好聚）&lt;/strong&gt;——反滥用团队 CAT 内部建立了具备生产知识的 Ares 小组，SRE 通过&amp;quot;交流计划&amp;quot;转移知识后体面退出，CAT 开发速度反而提升；&lt;strong&gt;数据分析管道（好散）&lt;/strong&gt;——合作十年后关系恶化（旧系统到极限、沟通破裂），最终解散 SRE 团队、平稳移交，教训是&amp;quot;如果关系健康，本可以暂时交还再取回&amp;quot;。&lt;/p&gt;
&lt;h3 id=&#34;113-sre-团队生命周期第-20-章&#34;&gt;11.3 SRE 团队生命周期（第 20 章）&lt;/h3&gt;
&lt;p&gt;从 0 到 1 的路径，三条原则贯穿：&lt;strong&gt;①SRE 需要有结果的 SLO；②SRE 必须有时间让明天比今天更好；③SRE 团队必须能调节自身工作量。&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    A[&amp;#34;没有 SRE？&amp;lt;br/&amp;gt;先用 SLO：承认不要 100% → 定 SLO → 写错误预算政策 → 测量并执行&amp;#34;] --&amp;gt; B[&amp;#34;招第一个 SRE&amp;lt;br/&amp;gt;考察：运维/软件工程/监控/自动化/架构&amp;#34;]
    B --&amp;gt; C[&amp;#34;组建 SRE 团队&amp;lt;br/&amp;gt;Tuckman 模型：组建 → 震荡 → 规范 → 表现&amp;#34;]
    C --&amp;gt; D[&amp;#34;多团队：按产品域或技术栈拆分&amp;lt;br/&amp;gt;⚠️ 警惕无人认领的组件&amp;#34;]
    D --&amp;gt; E[&amp;#34;地理分布式&amp;lt;br/&amp;gt;时区间隔 6-8 小时最佳，避开凌晨值班&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;警告：&lt;strong&gt;不要在还没实践 SRE 原则之前，把&amp;quot;运维团队&amp;quot;改名成&amp;quot;SRE 团队&amp;quot;&lt;/strong&gt;——品牌一旦失败，组织会彻底否定 SRE 这个概念。&lt;/p&gt;
&lt;h3 id=&#34;114-组织变革管理waze-的逆袭第-21-章&#34;&gt;11.4 组织变革管理：Waze 的逆袭（第 21 章）&lt;/h3&gt;
&lt;p&gt;Waze（2013 年被 Google 收购）的消息队列是全公司组件的通信命脉。吞吐量暴增后可靠性崩溃，最糟时&lt;strong&gt;整个 SRE 团队连续两周 24/7 救火，每小时手动重启部分组件&lt;/strong&gt;才能维持数千万用户的服务。&lt;/p&gt;
&lt;p&gt;变革路径（完美对应 Kotter 八步模型）：2 名 SRE + 1 名高级工程师组成&amp;quot;指导联盟&amp;quot;；评估现成产品后决定自研；招募志愿军先给占 30% 以上流量的单个组件建专用队列，挤出两个月的原型窗口；重写客户端库实现新旧系统流量切换；先迁低流量高重要性的消息流，双写提供备份路径。最终新系统承载旧系统 &lt;strong&gt;1000 倍以上&lt;/strong&gt;的负载，且几乎无需人工干预。第二轮变革是部署流程：引入 Spinnaker 持续交付，如今 &lt;strong&gt;Waze 超过 95% 的服务持续部署&lt;/strong&gt;，几乎无人工干预。&lt;/p&gt;
&lt;p&gt;书里还有一条推广通用工具的朴素经验：迁移成本必须接近零（&amp;ldquo;重新编译就能用上&amp;rdquo;），收益必须明确（&amp;ldquo;现在你已经不受某某漏洞影响&amp;rdquo;），衡量推广成功的第一指标是&lt;strong&gt;采用率&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id=&#34;十二结语把这本书带走的五句话&#34;&gt;十二、结语：把这本书带走的五句话&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;没有 SLO，就不需要 SRE&lt;/strong&gt;——先量化&amp;quot;什么叫足够可靠&amp;quot;，再谈可靠性工程。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;错误预算是开发速度与稳定性的通用货币&lt;/strong&gt;——预算充足就大胆发布，花光就冻结变更，不用吵架。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;琐事不超过 50%，修根因不修症状&lt;/strong&gt;——每小时损失多少钱，把账算给决策的人听。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缓解优先于根因，对事不对人&lt;/strong&gt;——先把用户救回来，再写一份有 P0 行动项的事后总结。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SRE 不是 Google 专利&lt;/strong&gt;——Home Depot 一年从 0 到 800 个服务，&amp;ldquo;如果我们能做到，您也可以。&amp;rdquo;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;最后用全书结尾的祝福语收个尾：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;愿你的查询流畅，愿你的传呼机保持沉默。&lt;/strong&gt;
（May your queries flow and your pagers stay silent.）&lt;/p&gt;
&lt;/blockquote&gt;
</description>
    </item>
    <item>
      <title>《代码大全》学习总结：写给新人的实用编程知识与技巧</title>
      <link>https://ftmao.com/blog/bookstore/code-complete-learning-notes/</link>
      <pubDate>Sun, 15 Mar 2020 10:00:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/bookstore/code-complete-learning-notes/</guid>
      <description>&lt;h1 id=&#34;代码大全学习总结写给新人的实用编程知识与技巧&#34;&gt;《代码大全》学习总结：写给新人的实用编程知识与技巧&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;参考书籍：Steve McConnell《代码大全（第 2 版）》&lt;br&gt;
目标读者：刚入行 1～3 年，想把代码写得更好、更稳、更容易维护的程序员。&lt;br&gt;
阅读目标：不追求背下所有规则，而是建立“好代码长什么样”的直觉。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;《代码大全》是一本很厚的书（中文版 900 多页），但它并不是一本讲某种语言语法的技术书，而是一本讲“&lt;strong&gt;怎样把程序写好&lt;/strong&gt;”的书。它覆盖的范围非常广：从需求、设计、构建，到命名、函数、调试、测试、代码布局、性能优化，几乎囊括了程序员日常工作的方方面面。&lt;/p&gt;
&lt;p&gt;这篇文章不会复述全书内容，而是挑出对新人最有用的部分，用通俗易懂的语言、图表和实例帮你建立主干框架。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;0-写在前面为什么这本书值得读&#34;&gt;0. 写在前面：为什么这本书值得读&lt;/h2&gt;
&lt;p&gt;很多新人在学完一门语言后，会有一种错觉：&lt;strong&gt;“我会写代码了”&lt;/strong&gt;。但真正进入项目后，才会发现：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;需求改了三版，代码已经改不动了；&lt;/li&gt;
&lt;li&gt;自己写的函数，两周后就看不懂了；&lt;/li&gt;
&lt;li&gt;一个 bug 改了三天，越改越乱；&lt;/li&gt;
&lt;li&gt;团队协作时，别人看不懂你的代码，你也看不懂别人的。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些问题往往不是“技术不够”，而是&lt;strong&gt;代码构建的基本功不扎实&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;《代码大全》想告诉我们的核心道理是：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;写代码不是艺术，而是一门工程手艺。好代码是可以通过学习、练习和检查清单逐步打磨出来的。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;下面我会按照“软件构建的全流程”来组织内容，让你知道每个阶段该注意什么。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;1-软件构建写代码只是其中一环&#34;&gt;1. 软件构建：写代码只是其中一环&lt;/h2&gt;
&lt;p&gt;很多新人以为程序员的工作就是“写代码”。其实，&lt;strong&gt;写代码只是“软件构建（Construction）”的一部分&lt;/strong&gt;，而构建又是整个软件开发生命周期的一部分。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    A[问题定义] --&amp;gt; B[需求分析]
    B --&amp;gt; C[架构设计]
    C --&amp;gt; D[详细设计]
    D --&amp;gt; E[编码与构建]
    E --&amp;gt; F[调试与测试]
    F --&amp;gt; G[集成与部署]
    G --&amp;gt; H[维护与重构]
    style E fill:#e1f5e1&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;11-为什么前期准备很重要&#34;&gt;1.1 为什么前期准备很重要&lt;/h3&gt;
&lt;p&gt;《代码大全》反复强调：&lt;strong&gt;前期准备能帮你省下后期大量的返工时间&lt;/strong&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;问题定义不清楚&lt;/strong&gt;：你可能解决了一个错误的问题；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;需求没对齐&lt;/strong&gt;：写到一半发现方向错了；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;架构没想好&lt;/strong&gt;：后期改结构成本极高；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;设计没做&lt;/strong&gt;：代码边写边改，容易变成“面条代码”。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;新人常见误区&lt;/strong&gt;：拿到需求立刻敲键盘。正确的做法是先用 10% 的时间想清楚“我要做什么、怎么做、边界在哪里”。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;12-构建阶段的核心产出&#34;&gt;1.2 构建阶段的核心产出&lt;/h3&gt;
&lt;p&gt;构建阶段的主要产出是&lt;strong&gt;高质量的源代码&lt;/strong&gt;。它包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;准确实现设计；&lt;/li&gt;
&lt;li&gt;写出可读的函数和类；&lt;/li&gt;
&lt;li&gt;处理异常和边界情况；&lt;/li&gt;
&lt;li&gt;保持代码风格一致；&lt;/li&gt;
&lt;li&gt;为测试和调试留下空间。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;2-命名代码可读性的第一道门槛&#34;&gt;2. 命名：代码可读性的第一道门槛&lt;/h2&gt;
&lt;p&gt;命名是编程中最频繁、也最容易被低估的动作。好名字能让代码“自解释”，坏名字会让阅读者每隔几秒就要停下来猜意思。&lt;/p&gt;
&lt;h3 id=&#34;21-好名字的标准&#34;&gt;2.1 好名字的标准&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;标准&lt;/th&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;说明&lt;/th&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;反例&lt;/th&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;正例&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;描述意图&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;名字要说明它是做什么的&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;&lt;code&gt;d&lt;/code&gt;&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;&lt;code&gt;elapsedTimeInDays&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;避免歧义&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;不要让人猜测含义&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;&lt;code&gt;data&lt;/code&gt;&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;&lt;code&gt;userProfile&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;保持一致&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;同类概念用同类词汇&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;&lt;code&gt;getUserInfo&lt;/code&gt; / &lt;code&gt;fetchOrderDetail&lt;/code&gt;&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;&lt;code&gt;getUser&lt;/code&gt; / &lt;code&gt;getOrder&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;避免缩写&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;除非团队公认&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;&lt;code&gt;cnt&lt;/code&gt;&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;&lt;code&gt;count&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;区分状态&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;布尔值用 is/has/should&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;&lt;code&gt;flag&lt;/code&gt;&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;&lt;code&gt;isValid&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;22-命名的思考流程&#34;&gt;2.2 命名的思考流程&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    A[这个变量/函数/类代表什么] --&amp;gt; B[它的核心职责是什么]
    B --&amp;gt; C[用 2～4 个词能说明白吗]
    C --&amp;gt;|能| D[直接使用描述性名称]
    C --&amp;gt;|不能| E[是否职责过多需要拆分]
    D --&amp;gt; F[检查是否有同义词/缩写混淆]
    E --&amp;gt; G[拆分为多个更小的单元]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;23-一个命名改写的例子&#34;&gt;2.3 一个命名改写的例子&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 坏例子&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;calc&lt;/span&gt;(a, b):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; a &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; b
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 好例子&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;calculate_rectangle_area&lt;/span&gt;(width, height):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; width &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; height&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;第二个版本不需要注释，看一眼就知道函数在做什么。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;3-函数程序的基本构建块&#34;&gt;3. 函数：程序的基本构建块&lt;/h2&gt;
&lt;p&gt;函数是代码复用和逻辑组织的基本单位。写好函数，代码质量会提升一大截。&lt;/p&gt;
&lt;h3 id=&#34;31-好函数的几个原则&#34;&gt;3.1 好函数的几个原则&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;单一职责&lt;/strong&gt;：一个函数只做一件事。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;短小&lt;/strong&gt;：尽量控制在 20～50 行以内，过长的函数往往职责过多。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;参数要少&lt;/strong&gt;：0～3 个参数最好，超过 3 个考虑封装成对象。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;避免副作用&lt;/strong&gt;：函数名是 &lt;code&gt;getUser&lt;/code&gt;，就不要顺手修改了全局状态。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;返回结果要清晰&lt;/strong&gt;：要么返回结果，要么抛出异常，不要混在一起。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;32-函数设计的决策流程&#34;&gt;3.2 函数设计的决策流程&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    A[我要完成一个任务] --&amp;gt; B[这个任务可以拆成几步]
    B --&amp;gt; C[每一步写成一个函数]
    C --&amp;gt; D[函数是否只做一个事]
    D --&amp;gt;|否| E[继续拆分]
    D --&amp;gt;|是| F[参数是否超过 3 个]
    F --&amp;gt;|是| G[封装成对象或配置类]
    F --&amp;gt;|否| H[命名是否清楚表达意图]
    H --&amp;gt;|否| I[重新命名]
    H --&amp;gt;|是| J[完成]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;33-踩坑实例一个函数承担了太多&#34;&gt;3.3 踩坑实例：一个函数承担了太多&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 坏例子：一个函数做了四件事&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;process_order&lt;/span&gt;(order):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# 1. 校验库存&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;not&lt;/span&gt; check_inventory(order):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;False&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# 2. 计算价格&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    price &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; order&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;quantity &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; order&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;unit_price
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; order&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;vip:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        price &lt;span style=&#34;color:#f92672&#34;&gt;*=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.9&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# 3. 扣款&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    charge_result &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; payment_gateway&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;charge(order&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;user_id, price)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;not&lt;/span&gt; charge_result:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;False&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# 4. 发货&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    shipping&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;create_shipment(order)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;True&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;这样的函数很难测试、很难复用、也很难改。更好的做法是拆分成多个职责单一的函数：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 好例子：每个函数只做一件事&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;validate_order&lt;/span&gt;(order):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; check_inventory(order)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;calculate_order_price&lt;/span&gt;(order):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    price &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; order&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;quantity &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; order&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;unit_price
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; apply_vip_discount(price, order&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;vip)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;charge_order&lt;/span&gt;(order, price):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; payment_gateway&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;charge(order&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;user_id, price)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ship_order&lt;/span&gt;(order):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; shipping&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;create_shipment(order)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;process_order&lt;/span&gt;(order):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;not&lt;/span&gt; validate_order(order):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;False&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    price &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; calculate_order_price(order)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;not&lt;/span&gt; charge_order(order, price):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;False&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; ship_order(order)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;感悟&lt;/strong&gt;：函数拆分不是“为了短而短”，而是让每一层逻辑都清晰、可测试、可替换。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;4-防御式编程代码要对外界保持怀疑&#34;&gt;4. 防御式编程：代码要对外界保持怀疑&lt;/h2&gt;
&lt;p&gt;程序运行时，你永远不知道输入会是什么样子：用户可能乱填数据，接口可能返回异常，文件可能不存在，网络可能中断。&lt;strong&gt;防御式编程就是： assume nothing，对输入和状态保持怀疑&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id=&#34;41-防御式编程的核心做法&#34;&gt;4.1 防御式编程的核心做法&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    A[接收输入] --&amp;gt; B[验证参数合法性]
    B --&amp;gt; C[处理异常情况]
    C --&amp;gt; D[使用断言约束内部状态]
    D --&amp;gt; E[返回明确结果或报错]
    E --&amp;gt; F[记录关键日志]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;42-具体技巧&#34;&gt;4.2 具体技巧&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;技巧&lt;/th&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;说明&lt;/th&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;例子&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;参数校验&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;不信任外部输入&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;检查 null、空字符串、越界值&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;异常处理&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;错误路径也要处理&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;文件不存在时给出友好提示&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;断言&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;对内部不变量做保护&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;&lt;code&gt;assert index &amp;gt;= 0&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;默认值&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;为可选值提供安全兜底&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;&lt;code&gt;timeout = timeout or 30&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;错误日志&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;出错时保留上下文&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;记录输入参数、调用栈、时间&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;43-踩坑实例忘记处理边界&#34;&gt;4.3 踩坑实例：忘记处理边界&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 坏例子&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;get_user_name&lt;/span&gt;(users, index):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; users[index]&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;name
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 好例子&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;get_user_name&lt;/span&gt;(users, index):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; users &lt;span style=&#34;color:#f92672&#34;&gt;is&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;None&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;raise&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ValueError&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;users list cannot be None&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; index &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;or&lt;/span&gt; index &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;=&lt;/span&gt; len(users):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;raise&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;IndexError&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;index &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{&lt;/span&gt;index&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; out of range for users list&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    user &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; users[index]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; user &lt;span style=&#34;color:#f92672&#34;&gt;is&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;None&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;raise&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ValueError&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;user at index &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{&lt;/span&gt;index&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; is None&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; user&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;name&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;第二个版本虽然长，但它明确告诉调用者：&lt;strong&gt;什么情况下会出错、为什么出错&lt;/strong&gt;。这对排障非常重要。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;感悟&lt;/strong&gt;：防御式编程不是“把代码写复杂”，而是“把失败路径也纳入设计”。一个只考虑正常路径的程序，上线后一定会遇到意外。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;5-变量与数据让状态可控&#34;&gt;5. 变量与数据：让状态可控&lt;/h2&gt;
&lt;p&gt;变量是程序状态的载体。变量用不好，代码就会充满“魔法值”和“隐式依赖”。&lt;/p&gt;
&lt;h3 id=&#34;51-变量使用的最佳实践&#34;&gt;5.1 变量使用的最佳实践&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;声明时初始化&lt;/strong&gt;：不要声明一个变量后过了很久才赋值。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缩小作用域&lt;/strong&gt;：能用局部变量就不要用全局变量。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;避免魔法数字&lt;/strong&gt;：用常量或枚举代替裸数字。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;一次只干一件事&lt;/strong&gt;：不要一个变量身兼数职。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;命名反映生命周期&lt;/strong&gt;：临时变量、循环变量也要有清晰名字。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;52-魔法数字改造示例&#34;&gt;5.2 魔法数字改造示例&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 坏例子&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; status &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    send_reminder()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 好例子&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ORDER_STATUS_PENDING_PAYMENT &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; status &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; ORDER_STATUS_PENDING_PAYMENT:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    send_reminder()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;第二个版本不需要查文档，也不需要猜“3 是什么意思”。&lt;/p&gt;
&lt;h3 id=&#34;53-变量生命周期示意图&#34;&gt;5.3 变量生命周期示意图&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    A[变量声明] --&amp;gt; B[初始化]
    B --&amp;gt; C[使用]
    C --&amp;gt; D[释放/超出作用域]
    style B fill:#fff9c4
    style D fill:#ffcdd2&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;感悟&lt;/strong&gt;：好的变量管理，本质上是“让程序状态可见、可控、可预测”。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;6-代码布局与风格让阅读更顺畅&#34;&gt;6. 代码布局与风格：让阅读更顺畅&lt;/h2&gt;
&lt;p&gt;代码是写给人看的，顺便给机器执行。一致的代码布局能大幅降低团队的阅读成本。&lt;/p&gt;
&lt;h3 id=&#34;61-布局的核心原则&#34;&gt;6.1 布局的核心原则&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;原则&lt;/th&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;说明&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;一致性&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;团队统一一种风格，不要混用&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;垂直分组&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;相关代码放在一起，用空行分隔逻辑块&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;水平对齐&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;适当的缩进和对齐，体现层级关系&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;注释说明“为什么”&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;不要复述代码做了什么，要解释为什么这么做&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;避免过度注释&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;代码能自解释的，就不要注释&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;62-代码布局示例&#34;&gt;6.2 代码布局示例&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 坏例子：格式混乱，缺少空行&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;calc&lt;/span&gt;(a,b,c):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; a&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        x&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;b&lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt;c
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; x&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;a
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 好例子：结构清晰&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;calculate_total&lt;/span&gt;(base_price, quantity, tax_rate):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; base_price &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    subtotal &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; base_price &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; quantity
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    tax &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; subtotal &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; tax_rate
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; subtotal &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; tax&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;7-调试与测试不要害怕-bug&#34;&gt;7. 调试与测试：不要害怕 bug&lt;/h2&gt;
&lt;p&gt;《代码大全》把调试和测试也归为“软件构建”的重要组成部分。对于新人来说，建立正确的调试观念比掌握某个调试工具更重要。&lt;/p&gt;
&lt;h3 id=&#34;71-调试的正确心态&#34;&gt;7.1 调试的正确心态&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    A[发现 Bug] --&amp;gt; B[不要急着改代码]
    B --&amp;gt; C[复现问题]
    C --&amp;gt; D[定位根因]
    D --&amp;gt; E[编写测试覆盖该场景]
    E --&amp;gt; F[修复并验证]
    F --&amp;gt; G[复盘：为什么会引入]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;72-测试的价值&#34;&gt;7.2 测试的价值&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;单元测试&lt;/strong&gt;：验证最小功能单元是否正确；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;集成测试&lt;/strong&gt;：验证多个模块协作是否正常；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;回归测试&lt;/strong&gt;：改完代码后，确保旧功能没坏。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;感悟&lt;/strong&gt;：调试不是“找错”，而是“理解程序为什么会这样表现”。很多时候，bug 是设计或假设的问题，不是代码敲错了。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;8-代码质量的良性循环&#34;&gt;8. 代码质量的良性循环&lt;/h2&gt;
&lt;p&gt;好代码不是一次性写成的，而是通过持续的小改进积累出来的。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    A[清晰命名] --&amp;gt; B[短小函数]
    B --&amp;gt; C[防御式编程]
    C --&amp;gt; D[充分测试]
    D --&amp;gt; E[持续重构]
    E --&amp;gt; A
    style A fill:#e1f5e1
    style B fill:#e1f5e1
    style C fill:#e1f5e1
    style D fill:#e1f5e1
    style E fill:#e1f5e1&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;这个循环的每一个环节都在降低系统的复杂度，让代码越来越容易维护。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;9-我的思考与感悟&#34;&gt;9. 我的思考与感悟&lt;/h2&gt;
&lt;h3 id=&#34;91-编程是手艺不是灵感&#34;&gt;9.1 编程是手艺，不是灵感&lt;/h3&gt;
&lt;p&gt;很多新人（包括我当初）觉得“写代码靠灵感”。但《代码大全》让我意识到，&lt;strong&gt;优秀代码背后是一系列可学习、可练习、可检查的技术&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;命名怎么取、函数怎么拆、异常怎么处理、代码怎么布局——这些都有明确的原则。掌握了原则，再结合实际项目不断练习，代码质量自然会提升。&lt;/p&gt;
&lt;h3 id=&#34;92-能跑不是终点&#34;&gt;9.2 “能跑”不是终点&lt;/h3&gt;
&lt;p&gt;初学编程时，我经常以“代码能跑”为标准。但工作中逐渐发现：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;能跑的代码，一个月后可能自己都看不懂；&lt;/li&gt;
&lt;li&gt;能跑的代码，加一个新需求可能要改很多地方；&lt;/li&gt;
&lt;li&gt;能跑的代码，给别人 review 时会被打回来重写。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;好的代码不仅要正确，还要可读、可维护、可扩展。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&#34;93-不要一次性追求完美&#34;&gt;9.3 不要一次性追求完美&lt;/h3&gt;
&lt;p&gt;《代码大全》不是要你一次性写出完美代码，而是鼓励&lt;strong&gt;持续改进&lt;/strong&gt;。先让代码工作，再让它正确，再让它清晰，最后才考虑性能优化。过早优化往往是万恶之源。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;10-踩坑实践我真实犯过的错&#34;&gt;10. 踩坑实践：我真实犯过的错&lt;/h2&gt;
&lt;h3 id=&#34;101-坑一函数太长逻辑纠缠&#34;&gt;10.1 坑一：函数太长，逻辑纠缠&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;场景&lt;/strong&gt;：写一个用户注册接口，把所有逻辑塞在一个函数里：校验参数、查数据库、发邮件、写日志。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;后果&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;单元测试没法写，只能端到端测；&lt;/li&gt;
&lt;li&gt;邮件服务挂掉，整个注册接口都失败；&lt;/li&gt;
&lt;li&gt;改一个小逻辑，要滚动好几屏。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;改进&lt;/strong&gt;：按照职责拆分成 &lt;code&gt;validate_input&lt;/code&gt;、&lt;code&gt;check_duplicate&lt;/code&gt;、&lt;code&gt;create_user&lt;/code&gt;、&lt;code&gt;send_welcome_email&lt;/code&gt;、&lt;code&gt;log_registration&lt;/code&gt; 等函数。&lt;/p&gt;
&lt;h3 id=&#34;102-坑二变量命名太随意&#34;&gt;10.2 坑二：变量命名太随意&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;场景&lt;/strong&gt;：用 &lt;code&gt;a&lt;/code&gt;、&lt;code&gt;b&lt;/code&gt;、&lt;code&gt;tmp&lt;/code&gt;、&lt;code&gt;data&lt;/code&gt; 作为变量名。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;后果&lt;/strong&gt;：两周后回来看代码，完全不知道这些变量代表什么，调试时也非常痛苦。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;改进&lt;/strong&gt;：每个变量名都尽量说明它的含义和用途，比如 &lt;code&gt;rawCsvContent&lt;/code&gt;、&lt;code&gt;parsedUserList&lt;/code&gt;、&lt;code&gt;retryCount&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id=&#34;103-坑三不做参数校验&#34;&gt;10.3 坑三：不做参数校验&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;场景&lt;/strong&gt;：写一个根据用户 ID 查询信息的函数，直接拿参数去查数据库。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;后果&lt;/strong&gt;：某天前端传了一个空字符串，导致 SQL 报错；更严重时可能被注入攻击。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;改进&lt;/strong&gt;：在入口层就校验参数类型、长度、范围，不信任任何外部输入。&lt;/p&gt;
&lt;h3 id=&#34;104-坑四过度注释&#34;&gt;10.4 坑四：过度注释&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;场景&lt;/strong&gt;：每一行代码都加注释，生怕别人看不懂。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;后果&lt;/strong&gt;：注释和代码不同步，反而误导阅读者；真正需要解释的地方反而没注释。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;改进&lt;/strong&gt;：注释说明“为什么”和“设计意图”，代码本身说明“做了什么”。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;11-给新人的学习建议&#34;&gt;11. 给新人的学习建议&lt;/h2&gt;
&lt;p&gt;如果你刚读完《代码大全》或者准备读，可以按照下面这个路线来：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    A[先读第 1～7 章&amp;lt;br/&amp;gt;建立构建全景] --&amp;gt; B[重点读命名、函数、防御式编程]
    B --&amp;gt; C[结合自己项目&amp;lt;br/&amp;gt;刻意练习]
    C --&amp;gt; D[做一份个人代码检查清单]
    D --&amp;gt; E[Review 别人代码&amp;lt;br/&amp;gt;也接受别人 Review]
    E --&amp;gt; F[定期重构旧代码]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;推荐建立的个人检查清单&#34;&gt;推荐建立的个人检查清单&lt;/h3&gt;
&lt;p&gt;在提交代码前，问自己几个问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; 变量和函数名是否能清楚表达意图？&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; 每个函数是否只负责一件事？&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; 参数数量是否控制在 3 个以内？&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; 是否处理了异常和边界情况？&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; 是否有重复代码可以抽取？&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; 是否添加了必要的测试？&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; 注释是否说明了“为什么”而不是复述“做什么”？&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;12-结语&#34;&gt;12. 结语&lt;/h2&gt;
&lt;p&gt;《代码大全》是一本值得放在手边、反复翻阅的书。它不会教你某个框架怎么用，也不会教你某种语言的语法，但它会告诉你：&lt;strong&gt;一个专业程序员应该怎样思考和构建代码&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;对于新人来说，不需要一次性读完。可以先建立整体认知，再在遇到具体问题时回头查对应章节。最重要的是，把书中的原则应用到日常编码中，逐渐形成自己的代码审美。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;好代码不是写出来的，是改出来的。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;希望这篇文章能帮你少走一些弯路，写出更干净、更可靠的代码。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;参考书籍&lt;/strong&gt;：Steve McConnell《代码大全（第 2 版）》&lt;br&gt;
&lt;strong&gt;延伸阅读&lt;/strong&gt;：《重构》《代码整洁之道》《程序员修炼之道》&lt;/p&gt;
</description>
    </item>
    
  </channel>
</rss>
