<?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/%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95/</link>
    <description>AI 视频、AI Agent 与工程实践笔记</description>
    <generator>Hugo</generator>
    <language>zh-CN</language>
    <lastBuildDate>Tue, 08 Aug 2023 10:40:00 +0800</lastBuildDate><atom:link href="https://ftmao.com/blog/tags/%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95/index.xml" rel="self" type="application/rss+xml" /><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>
    
  </channel>
</rss>
