<?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/tags/%E4%BB%A3%E7%A0%81%E8%B4%A8%E9%87%8F/</link>
    <description>AI 视频、AI Agent 与工程实践笔记</description>
    <generator>Hugo</generator>
    <language>zh-CN</language>
    <lastBuildDate>Mon, 19 Jan 2026 16:00:00 +0800</lastBuildDate><atom:link href="https://ftmao.com/blog/tags/%E4%BB%A3%E7%A0%81%E8%B4%A8%E9%87%8F/index.xml" rel="self" type="application/rss+xml" /><item>
      <title>高 bug 率的人该怎么对待：一场项目争论背后的对事、对人与系统</title>
      <link>https://ftmao.com/blog/posts/high-bug-rate-blame-or-system/</link>
      <pubDate>Mon, 19 Jan 2026 16:00:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/posts/high-bug-rate-blame-or-system/</guid>
      <description>&lt;h1 id=&#34;高-bug-率的人该怎么对待一场项目争论背后的对事对人与系统&#34;&gt;高 bug 率的人该怎么对待：一场项目争论背后的对事、对人与系统&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;引子是一次真实的项目讨论。组里有位同事，bug 率明显比其他人高——高到 QA 看见他的名字心里先咯噔一下。评审会上大家争论怎么处理，冒出来的意见很能代表一个团队的所有本能：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;「对事不对人，先别急着怪他，看看是不是模块本身烂。」&lt;/li&gt;
&lt;li&gt;「烂模块别人也在写，怎么就他 bug 多？这就是人的问题，该敲打敲打。」&lt;/li&gt;
&lt;li&gt;「要我说这是架构问题，没测试没 CI，谁写都得出事。」&lt;/li&gt;
&lt;li&gt;「都 2026 年了，他连 AI 辅助 review、AI 生成测试都不用，bug 多不是活该吗？」&lt;/li&gt;
&lt;li&gt;「你们都想简单了，这模块需求天天变、接口文档是过期的，这是设计问题。」&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;五种说法，五种药方，互相打架。这场争论当时没有结论，但它逼我把一个问题想透了：&lt;strong&gt;「高 bug 率」根本不是一个问题，而是一组完全不同的问题共用的一个症状。&lt;/strong&gt; 这篇文章就是我后来理出来的完整处理路径：先把症状变成数据，再用方法论找全病根，然后对照行业里的成熟做法，最后才轮到那个最初的问题——对事，还是对人。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;一第一步把高-bug-率变成数据&#34;&gt;一、第一步：把「高 bug 率」变成数据&lt;/h2&gt;
&lt;p&gt;「bug 率高」是一个印象，不是事实。争论对错之前，至少要回答三个数据问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;分母是什么？&lt;/strong&gt; 按提交算、按功能点算，还是按代码行算？写核心复杂模块的人和写边角页面的人，天然不在一个 bug 密度量级上。不比单位产出只比绝对数量，是争论里最常见的暗坑。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;分布长什么样？&lt;/strong&gt; 他的 bug 集中在某个模块，还是均匀散布在他写的所有代码里？&lt;strong&gt;同一个模块换谁写都 bug 多，是系统的问题；同一个人换什么模块都 bug 多，才轮到看人。&lt;/strong&gt; 这一条能砍掉一半的争论。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;bug 的类型是什么？&lt;/strong&gt; 是低级错误（空指针、边界、拼写），还是逻辑误解（需求理解错、边界场景没想到），还是集成事故（改了 A 弄坏 B）？三种 bug 指向三种完全不同的病根。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;没有这三组数据，会议上的所有「我觉得」都只是立场。这也引出一个朴素的道理：&lt;strong&gt;对事不对人的第一步，是先把「事」看清楚——多数争论不是因为价值观不同，而是因为大家在争论一个没被测量过的东西。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&#34;二第二步逐条验证五种说法归并成四种病根&#34;&gt;二、第二步：逐条验证，五种说法归并成四种病根&lt;/h2&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;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;td&gt;先教练，无效再惩戒&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;「架构问题」＋「AI 用得太少」&lt;/td&gt;
					&lt;td&gt;全组在该模块都偏高；逃逸率高（漏到线上才发现）；重复性低级错误占比高、测试覆盖率远低于组均&lt;/td&gt;
					&lt;td&gt;工程系统缺位：无测试、无 CI、高耦合、反馈慢，AI 工具链未流程化&lt;/td&gt;
					&lt;td&gt;补工程系统（含 AI 工具链默认化），不怪任何人&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;「设计问题」&lt;/td&gt;
					&lt;td&gt;bug 集中在「需求理解偏差」类；返工率高&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;运气 + 单点疏忽&lt;/td&gt;
					&lt;td&gt;复盘机制，不动人&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;为什么「AI 用得太少」和「架构问题」并成一行？因为用要素维度分解（后文的鱼骨图），AI 工具链属于「机」这一格：团队没把 AI review、AI 生成测试、CI 静态检查做成流程默认项，&lt;strong&gt;这是系统缺位，不是哪个成员的问题&lt;/strong&gt;。只有一种情况它退化回「人」：工具链已经是默认项，而个人拒绝使用——那属于习惯与技能问题，交给后文的 Just Culture 框架处理。&lt;/p&gt;
&lt;p&gt;这个 2026 年的新变量值得多说两句，因为它有正反两面。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;正面：「AI 用得不够」首先暴露的是系统问题，不是个人懒惰。&lt;/strong&gt; 如果团队没有默认开启 AI 辅助 review、没有用 AI 生成测试基线，那么「某个成员不用 AI」暴露的是工程系统的缺失——别人不用你也发现不了，只不过他 bug 多先暴露了。正确的动作是把 AI 工具链做成流程默认项，让所有产出都过一遍机器筛子，而不是点名批评谁「不拥抱新技术」。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;反面：AI 生成的代码不经 review 直接提交，是 2026 年高 bug 率的新来源。&lt;/strong&gt; 见过不止一个案例，当事人解释「这是 AI 写的，我大意了」——这不能作为免责理由。AI 是放大器，责任心仍是人的。用 AI 放大产出可以，用 AI 稀释 review 不行。规矩要立在团队层面：AI 生成的代码，提交人负全责。&lt;/p&gt;
&lt;p&gt;另外单独提醒「设计问题」：它是最容易被冤枉成「人的问题」的一种。需求一句话、接口靠口头、边界场景靠猜——这种环境下产出的 bug，记在程序员头上是冤案。判断方法很简单：把他的 bug 逐条归因，如果一半以上可以追溯到「需求没说清」，那该改的是需求评审流程，不是他。&lt;/p&gt;
&lt;h2 id=&#34;三第三步病根怎么找全三层归因方法论&#34;&gt;三、第三步：病根怎么找全——三层归因方法论&lt;/h2&gt;
&lt;p&gt;五种说法来自一场会议的即兴发挥，够用吗？不够。它漏掉的病根我在不同团队真实遇到过：排期压垮质量、度量口径本身失真、外部依赖的脏数据、频繁切换任务导致的认知断裂……经验枚举永远有盲区，需要的是一套能&lt;strong&gt;生成&lt;/strong&gt;假设、保证不重不漏的方法论。我用三把刀。&lt;/p&gt;
&lt;h3 id=&#34;第一把刀引入链分解按-bug-的生命周期切&#34;&gt;第一把刀：引入链分解——按 bug 的生命周期切&lt;/h3&gt;
&lt;p&gt;每个 bug 都有完整的生命周期：&lt;strong&gt;需求 → 设计 → 实现 → 验证 → 集成 → 发布 → 运行&lt;/strong&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;需求模糊、频繁变更、边界未定义&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;设计问题&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;实现&lt;/td&gt;
					&lt;td&gt;人的能力/习惯、AI 生成未审查&lt;/td&gt;
					&lt;td&gt;人的问题&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;验证&lt;/td&gt;
					&lt;td&gt;无测试、review 走过场、AI 工具链缺位&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;架构问题&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;会上的说法覆盖了链条的前五环，却漏了最后一环——线上 bug 里有相当比例是环境、数据、依赖造成的，把这类算在程序员头上是彻头彻尾的冤案。&lt;/p&gt;
&lt;h3 id=&#34;第二把刀鱼骨图-6m按要素维度切&#34;&gt;第二把刀：鱼骨图 6M——按要素维度切&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 LR
    R[&amp;#34;高 bug 率&amp;#34;] --&amp;gt; M1[&amp;#34;人&amp;lt;br/&amp;gt;能力 / 习惯 / 态度 / 状态&amp;#34;]
    R --&amp;gt; M2[&amp;#34;机&amp;lt;br/&amp;gt;工具链 / AI / 编译检查 / IDE&amp;#34;]
    R --&amp;gt; M3[&amp;#34;料&amp;lt;br/&amp;gt;需求质量 / 文档 / 输入数据&amp;#34;]
    R --&amp;gt; M4[&amp;#34;法&amp;lt;br/&amp;gt;流程 / 评审 / 排期 / 协作方式&amp;#34;]
    R --&amp;gt; M5[&amp;#34;环&amp;lt;br/&amp;gt;环境 / 依赖服务 / 任务切换频率&amp;#34;]
    R --&amp;gt; M6[&amp;#34;测&amp;lt;br/&amp;gt;测试覆盖 / 度量口径 / 统计方法&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;6M 的价值在于它逼你检查&lt;strong&gt;平时想不到的维度&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;法&lt;/strong&gt;里面藏着排期——连续赶工期的团队，bug 率上升是生理现象，不是态度问题；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;环&lt;/strong&gt;里面藏着任务切换——一个人同时被拉在三个项目里，认知断裂本身就是 bug 发生器；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;测&lt;/strong&gt;里面藏着最容易被忽略的一条：&lt;strong&gt;度量口径本身可能就是病根&lt;/strong&gt;。bug 怎么算、谁录入、严不严格，直接扭曲「谁 bug 率高」的结论。度量一旦成为目标就不再是好的度量（古德哈特定律），拿失真的数据去惩人，是双重错误。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;两把刀交叉，病根空间基本不重不漏：链条保证流程维度不漏，6M 保证要素维度不漏。&lt;/p&gt;
&lt;h3 id=&#34;第三把刀控制变量把相关性变成归因&#34;&gt;第三把刀：控制变量——把相关性变成归因&lt;/h3&gt;
&lt;p&gt;枚举出假设只是开始，归因要靠实验设计。核心思想是方差分析：bug 率是人、模块、需求、流程的多元函数，&lt;strong&gt;每次只动一个变量，看结果怎么变&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;换人写同模块&lt;/strong&gt;：bug 率不降 → 模块/需求的问题；降了 → 继续往下查人；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;同一个人写别的模块&lt;/strong&gt;：还是高 → 人的嫌疑上升；正常了 → 原模块的问题；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;同一需求给两个人分别实现&lt;/strong&gt;：bug 模式相似 → 需求/设计的问题；差异大 → 实现者的问题；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;横向对照组&lt;/strong&gt;：全组在同类模块上的 bug 基线是多少？没有基线，「偏高」二字本身就不成立。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这一套换组实验做完，多数「人的问题」会现出系统问题的原形，剩下真正属于人的部分，才轮到第五节处理。&lt;/p&gt;
&lt;p&gt;最后补一个贯穿全程的视角：&lt;strong&gt;每个线上 bug 都是两个失败的乘积——引入失败 × 拦截失败。&lt;/strong&gt; 只问「谁引入的」是在追责，同时问「为什么六道防线一道都没拦住」才是在建设。前者最多改变一个人，后者改变的是所有人的未来 bug 率。&lt;/p&gt;
&lt;h2 id=&#34;四第四步行业里的成熟方法论与工具地图&#34;&gt;四、第四步：行业里的成熟方法论与工具地图&lt;/h2&gt;
&lt;p&gt;三把刀都不是我的发明：引入链分解借自 IBM 的 ODC，鱼骨图来自石川馨，控制变量是实验设计的基本功。行业里的原版比我的速记法成熟，这一节直接对照。&lt;/p&gt;
&lt;h3 id=&#34;归因层odc正交缺陷分类&#34;&gt;归因层：ODC（正交缺陷分类）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;ODC（Orthogonal Defect Classification，Chillarege 等人 1992 年提出）&lt;/strong&gt; 的核心思想比「按环节分类」更深一层：把每个缺陷沿&lt;strong&gt;正交维度&lt;/strong&gt;（缺陷类型、触发条件、影响、来源、发现阶段）打标签，然后看&lt;strong&gt;分布特征&lt;/strong&gt;而不是个案——某一类缺陷在某个阶段的分布异常，直接反推哪个流程环节出了系统性问题。缺陷不是待修的孤案，而是流程的测量数据。这套方法在 IBM、电信和航空软件里跑了三十多年。小团队不必全套上 ODC，但最低限度要做到：&lt;strong&gt;缺陷入库时强制填「引入阶段」和「缺陷类型」两个字段&lt;/strong&gt;——没有这两个字段，第一节要的三组数据永远凑不齐。&lt;/p&gt;
&lt;h3 id=&#34;度量层dora-指标与缺陷逃逸率&#34;&gt;度量层：DORA 指标与缺陷逃逸率&lt;/h3&gt;
&lt;p&gt;「bug 率高」的行业标准度量不是我拍脑袋的三个数据问题，而是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;DORA 四键指标&lt;/strong&gt;（《Accelerate》）：其中的&lt;strong&gt;变更失败率&lt;/strong&gt;就是「高 bug 率」最权威的同行参照系——顶级团队与垫底团队的差距是数量级的，而且它和其他三项（部署频率、变更前置时间、恢复时间）联动，单独压 bug 率而不管交付节奏是缘木求鱼；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缺陷逃逸率&lt;/strong&gt;：bug 在哪一环被发现（开发自测 / CI / 测试 / 线上），逃逸越靠后成本越高——这是「拦截失败」的标准度量；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缺陷聚集热力图&lt;/strong&gt;：Microsoft Research 多年的实证结论——&lt;strong&gt;缺陷不是均匀分布的，少数模块集中了多数缺陷&lt;/strong&gt;（与改动频率和复杂度强相关），CodeScene 这类工具可以直接画出热度图。这直接支撑了「换人写同模块」实验的必要性：热点模块的 bug 率天然高，不控变量就是在冤枉人。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;防线层shift-left-与工具链&#34;&gt;防线层：shift-left 与工具链&lt;/h3&gt;
&lt;p&gt;「拦截失败 × 引入失败」的乘积视角，行业里叫 &lt;strong&gt;shift-left（防线左移）&lt;/strong&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;工具（2026 年现网主流）&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;需求/设计&lt;/td&gt;
					&lt;td&gt;设计评审、ADR（架构决策记录）、契约先行&lt;/td&gt;
					&lt;td&gt;设计文档模板 + 评审清单&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;实现&lt;/td&gt;
					&lt;td&gt;测试金字塔（Google 的 small/medium/large 分层）、TDD、小步提交&lt;/td&gt;
					&lt;td&gt;JUnit/pytest + 覆盖率（只做参考，&lt;strong&gt;不做 KPI&lt;/strong&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;SonarQube、CodeQL、Semgrep、Snyk&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;测试有效性&lt;/td&gt;
					&lt;td&gt;变异测试（测试的测试）、模糊测试&lt;/td&gt;
					&lt;td&gt;PIT（Java）、fuzzing&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;代码审查&lt;/td&gt;
					&lt;td&gt;AI review 设为 CI 默认门禁&lt;/td&gt;
					&lt;td&gt;CodeRabbit（部署最广）、Greptile（全库上下文检出率最高）、Qodo（审查+测试生成）、GitHub Copilot Code Review、Cursor BugBot&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;测试生成&lt;/td&gt;
					&lt;td&gt;AI 生成单测基线&lt;/td&gt;
					&lt;td&gt;Diffblue Cover（Java）、Qodo&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;集成/发布&lt;/td&gt;
					&lt;td&gt;Trunk-Based Development、小 PR、CI 门禁、灰度&lt;/td&gt;
					&lt;td&gt;GitHub Actions / GitLab CI + 特性开关&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;两点强调：其一，2026 年的 AI review 工具已经过了尝鲜期——CodeRabbit 接入仓库数以百万计，Greptile 靠全代码库索引拿到最高检出率——&lt;strong&gt;「不配 AI 审查门禁」在今天的含义，和五年前「没有 CI」一样，属于工程系统缺位&lt;/strong&gt;，这印证了第二节把 AI 折进工程系统的判断。其二，覆盖率、检出数这些指标一旦变成 KPI 就失效，它们只能用来找盲区，不能用来排名。&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;Google SRE 的错误预算（error budget）&lt;/strong&gt;：服务的可靠性目标不是 100%，预算内随你折腾（宽），预算耗尽立刻冻结发布、全员还债（猛）。&lt;strong&gt;什么时候该宽、什么时候该猛，不再依赖管理者的手感，而是事先约定的数字&lt;/strong&gt;——这是「小惩大诫」最优雅的工程实现：惩戒自动触发、剂量事先封顶、对事不对人。书架上有我的《&lt;a href=&#34;https://ftmao.com/blog/blog/bookstore/google-sre-workbook-summary/&#34;&gt;Google SRE 工作手册&lt;/a&gt;》笔记，错误预算一章值得细看；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;丰田的安灯（andon）与防错（poka-yoke）&lt;/strong&gt;：流水线上任何人发现异常都可以拉灯停线——「对事不对人」不是口号，是给一线员工的制度性权力；防错设计则直接把「人会犯的错」设计成「犯不了的错」，比任何惩戒都高明。bug 率治理的终极目标不是把人管得更紧，而是让低级错误在机制上不可能发生。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以这一节的结论是：&lt;strong&gt;归因用 ODC 的思想，度量用 DORA 与逃逸率，防线用 shift-left 工具链，宽猛用错误预算制度化&lt;/strong&gt;——我的「三把刀」只是这些成熟方法论的穷人版速记，团队有条件就应该直接上原版。&lt;/p&gt;
&lt;h2 id=&#34;五第五步轮到人之后just-culture-三分法&#34;&gt;五、第五步：轮到「人」之后——Just Culture 三分法&lt;/h2&gt;
&lt;p&gt;排除了系统性因素，剩下的才轮到「人」——而处理人，最怕一刀切。安全科学里的 Just Culture（公正文化）把行为分成三类，每一类的正确回应完全不同：&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;/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;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;用「对事不对人」处理第三类，是在纵容——恶意和惰性会学习你的宽容；用「敲打」处理第一类，是在制造恐惧文化——从此 bug 都被藏起来，团队失去学习的输入。&lt;strong&gt;冲突从来不在两种哲学之间，而在用错了对象。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&#34;六更高处的统一系辞与-dalio&#34;&gt;六、更高处的统一：系辞与 Dalio&lt;/h2&gt;
&lt;p&gt;「对事不对人」和「该敲打敲打」这两种看似对立的姿态，恰好各有一部经典背书，而两部经典在更高处说的是同一件事。&lt;/p&gt;
&lt;p&gt;《系辞传》讲「小惩而大诫，此小人之福也」。注意上下文——它的对象是「不威不惩」的小人，即内在荣辱感已经失灵、只有外部后果能触动的人；注意落点——&lt;strong&gt;「福」&lt;/strong&gt;：惩罚是为受罚者本人的长远利益服务的。《系辞》下传还有一句「无有师保，如临父母」：管教的心是父母之心，不是权力之心。&lt;/p&gt;
&lt;p&gt;Dalio《原则》讲对事不对人、极端求真（radical truth）：当面指出问题比背后留情更仁慈，这是 tough love。&lt;/p&gt;
&lt;p&gt;两者在最高处是同一个标准：&lt;strong&gt;评价一次干预，不看「有没有怪罪人」，而看「它有没有让反馈回路闭合、有没有让这个人变得更好」。&lt;/strong&gt; 惩罚如果是为了发泄、为了立威，就同时背叛了系辞和 Dalio。孔子论政说「宽以济猛，猛以济宽，政是以和」——宽（对事不对人）与猛（小惩大诫）不是二选一，是因人因事因时动态配比的阴阳两端：只宽不猛，组织失去边界；只猛不宽，组织失去真话。&lt;/p&gt;
&lt;p&gt;而两者共同信仰的，是《系辞》最看重的那个字——&lt;strong&gt;几&lt;/strong&gt;：「几者，动之微，吉之先见者也。」反馈必须早、必须真、必须被感觉到。Dalio 用真话送达信号，「小惩」用后果送达信号；信道不同，信源相同。等到 bug 率高到要开会讨论才处理，已经是「不俟终日」的反面教材了。&lt;/p&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;graph TD
    A[&amp;#34;有人 bug 率明显偏高&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;补测试 / CI / 解耦 / AI 工具链&amp;lt;br/&amp;gt;不归咎任何人&amp;#34;]
    B -- &amp;#34;否, 集中在他&amp;#34; --&amp;gt; D{&amp;#34;看 bug 类型&amp;#34;}
    D -- &amp;#34;需求理解偏差类居多&amp;#34; --&amp;gt; E[&amp;#34;设计问题&amp;lt;br/&amp;gt;修需求评审与接口契约流程&amp;#34;]
    D -- &amp;#34;低级重复错误居多&amp;#34; --&amp;gt; F[&amp;#34;工程系统缺失&amp;lt;br/&amp;gt;AI review / 测试基线设为流程默认项&amp;#34;]
    D -- &amp;#34;集成事故类居多&amp;#34; --&amp;gt; G[&amp;#34;架构耦合问题&amp;lt;br/&amp;gt;修边界与回归机制&amp;#34;]
    D -- &amp;#34;跨模块一致的逻辑疏忽&amp;#34; --&amp;gt; H{&amp;#34;看人: 无心之失&amp;lt;br/&amp;gt;还是明知故犯 / 屡教不改?&amp;#34;}
    H -- &amp;#34;无心之失&amp;#34; --&amp;gt; I[&amp;#34;教练 + 结对 + 改系统&amp;lt;br/&amp;gt;绝不罚, 保护上报文化&amp;#34;]
    H -- &amp;#34;侥幸冒险 / 屡教不改&amp;#34; --&amp;gt; J[&amp;#34;小惩大诫&amp;lt;br/&amp;gt;及时 / 小剂量 / 成比例&amp;lt;br/&amp;gt;动手前自问: 如临父母还是发泄立威?&amp;#34;]&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;&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;八收尾&#34;&gt;八、收尾&lt;/h2&gt;
&lt;p&gt;回到那场争论。会后我做了归因分析，结论有点扫兴也有点解脱：那位同事 60% 的 bug 出在同一个无测试、需求三天一变的老模块——是系统问题；剩下的低级错误里，AI review 能拦掉一大半——是工程系统问题；真正属于他个人的，是重复犯同类边界错误、提醒后照旧——这部分，一次正式的、明确的、带着「这是为你好」的谈话处理了。三个月后，他的 bug 率回到了组内均值。&lt;/p&gt;
&lt;p&gt;所以「对事还是对人」是个伪问题。完整的答案是：&lt;strong&gt;先对事（数据归因），再对系统（方法论找全病根），最后才对人（Just Culture 分类处理）；对事不对人是道的常态，小惩大诫是道的边界——两者的根，都是让反馈回路不失灵。&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;延伸阅读：书架上《&lt;a href=&#34;https://ftmao.com/blog/blog/bookstore/principles-dalio-book-notes/&#34;&gt;原则&lt;/a&gt;》笔记里有 Dalio 的完整决策框架，《&lt;a href=&#34;https://ftmao.com/blog/blog/bookstore/thinking-in-systems-book-notes/&#34;&gt;系统之美&lt;/a&gt;》讲透了为什么「人的行为来自系统结构」，《&lt;a href=&#34;https://ftmao.com/blog/blog/bookstore/yijing-64-hexagrams-index/&#34;&gt;系辞传全文与白话译文&lt;/a&gt;》里有「小惩大诫」的原文语境。&lt;/p&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/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>《代码大全》学习总结：写给新人的实用编程知识与技巧</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>
