重构:AI 时代的代码进化
本机暂存
<blockquote><p>「Any fool can write code that a computer can understand. Good programmers write code that humans can understand.」<br>任何傻瓜都能写出计算机能看懂的代码。好程序员写的是人能看懂的代码。<br>—— Martin Fowler</p></blockquote><p>第 22 章解决了「人怎么读懂 AI 写的代码」,用 UML 把代码画成图。</p><p>读懂之后呢?你打开 AI 生成的代码,能跑,但是一团乱麻:一个方法三百行,一个类管了八件事,同样的逻辑复制了五遍。这时候怎么办?</p><p>重构。</p><p>这件事本身不新鲜,Martin Fowler 1999 年就把它写成了一本书。变的是执行者。以前是人对着那本书一处一处手动改,现在是 AI 对着同一本书的目录自动改,人退到后面审查 diff。</p><span id="more"></span><p><img src="/images/image-20260626094732641.png"></p><p>本章分两半。前半讲重构的理论根基:什么是重构、技术债是怎么回事、什么时候该重构、Fowler 编了哪些坏味道和手法。这部分主要参考 Fowler 的《重构》,以及把这本书做成在线可检索版本的 <a href="https://refactoring.guru/refactoring">refactoring.guru</a>。后半讲 goal workflow 套件里的两个 Skill:<code>/refactor</code>(介绍页:<a href="https://goal.rpcx.io/index_cn.html#step-refactor">https://goal.rpcx.io/index_cn.html#step-refactor</a> )把 Fowler 第 2 版的整个目录封进了一个 AI Agent 能直接调用的能力里;<code>/smell</code>(介绍页:<a href="https://goal.rpcx.io/index_cn.html#step-smell">https://goal.rpcx.io/index_cn.html#step-smell</a> )则在它前面一步,负责扫出整个代码库到底哪里有问题。一个诊断,一个治疗。</p><h2 id="23-1-什么是重构,什么是技术债"><a href="#23-1-什么是重构,什么是技术债" class="headerlink" title="23.1 什么是重构,什么是技术债"></a>23.1 什么是重构,什么是技术债</h2><p>重构(refactoring)是在不改变外部行为的前提下改善代码内部结构。说白了,把一团乱麻(dirty code)整理成清爽的代码(clean code),但程序对外的表现一字不变。</p><ul><li><strong>脏代码</strong>是经验不足,再加上 deadline、管理混乱、开发途中走捷径,几样凑一起的产物。</li><li><strong>整洁代码</strong>易读、易懂、易维护,它让开发变得可预测。你大概知道改一处要花多久,而不是每次都掉进未知的坑。</li></ul><p>代码为什么会从整洁滑向肮脏?Ward Cunningham 的<strong>技术债</strong>这个说法解释得很到位。走捷径、跳过测试,就像找银行贷款:眼下买东西是快了,但要付利息。你不光还本金,还得还利息;利息攒够了,甚至会超过你的全部收入,永远还不清。代码也一样。不写测试能暂时提速,可它每天都在拖你后腿,直到哪天你补上测试,把债还掉。</p><p><img src="/images/image-20260626095233920.png"></p><p>refactoring.guru 列了一串技术债的成因。这些在 AI 时代值得重读,因为大多被放大了:</p><ul><li><strong>业务压力</strong>:功能没做完就得上线,于是补丁和权宜之计堆上去,盖住没收尾的部分。</li><li><strong>看不到后果</strong>:管理层不知道技术债有「利息」,债越欠越多、开发越来越慢,于是舍不得给重构留时间。</li><li><strong>强耦合没人管</strong>:项目变成一块铁板而不是一堆独立模块,动一处牵全身。</li><li><strong>缺测试</strong>:没有即时反馈,人就敢用又快又险的 workaround。最坏的情况是没测试直接上线,可能给几千个客户发出一封诡异的测试邮件,也可能直接把数据库清空。</li><li><strong>缺文档、缺沟通</strong>:新人上手慢;关键的人一走,开发就停摆。</li><li><strong>长期多分支并行</strong>:隔离的改动越多,合并时积下的债越大。</li><li><strong>重构一拖再拖</strong>:需求一直变,旧代码总有显得笨重的一天;可程序员每天还在往旧代码上接新代码,所以拖得越久,将来要返工的依赖就越多。</li></ul><p>「缺测试」和「重构一拖再拖」这两条在 AI 时代尤其要命。Agent 几分钟就能吐出几千行代码,没有特征测试兜底、没有及时整理,技术债的本金和利息会以人类时代见不到的速度滚起来。第 12 章讲 GSD Core 时提过的「反合理化表」,对抗的正是「以后再重构」「先跳过测试」这类自欺。它其实就是技术债成因的制度化解药。</p><h2 id="23-2-何时重构:三次法则"><a href="#23-2-何时重构:三次法则" class="headerlink" title="23.2 何时重构:三次法则"></a>23.2 何时重构:三次法则</h2><p>Fowler 的经验法则很简洁——<strong>三次法则(Rule of Three)</strong>:</p><ol><li><strong>第一次</strong>做某件事,直接做完。</li><li><strong>第二次</strong>做类似的事,心里别扭也照旧重复一遍。</li><li><strong>第三次</strong>再碰到同类的事,开始重构。</li></ol><p>第一次重复忍着,第三次出现就动手。除此之外还有三个天然时机:</p><ul><li><strong>加功能时</strong>:重构能帮你读懂别人的代码。面对一坨脏代码,先重构再加,整洁的代码好改得多。这一改不只方便你,也方便后面接手的人。</li><li><strong>修 bug 时</strong>:bug 跟现实里的虫子一样,专挑代码最暗最脏的角落待着。把代码清干净,错误几乎会自己冒出来。</li><li><strong>代码评审时</strong>:这往往是代码公开前最后一次整理的机会。最好跟作者结对来做,简单问题当场修掉,复杂的估个时间再说。</li></ul><h2 id="23-3-如何重构:正确的姿势"><a href="#23-3-如何重构:正确的姿势" class="headerlink" title="23.3 如何重构:正确的姿势"></a>23.3 如何重构:正确的姿势</h2><p>重构是一连串小改动,每一步让代码好一点,同时让程序始终能跑。refactoring.guru 的「做对了」清单只有三条,却是整套方法论的核心:</p><ul><li><strong>代码必须变干净。</strong> 重构完还是一团乱,那这一小时就白花了。这种事多半发生在你丢掉「小步改」、把一大堆重构混进一次大改动的时候。尤其赶 deadline 的时候,很容易把自己绕晕。</li><li><strong>重构期间不写新功能。</strong> 别把重构和加特性搅在一起,至少在单个 commit 里把两件事分开。</li><li><strong>重构后所有已有测试都得过。</strong> 测试挂了只有两种可能:要么你改错了,修就是;要么你的测试太底层了,比如去测了类的私有方法,这种情况是测试的锅,该把它重写成更高层、BDD 风格的测试。</li></ul><p>记住这三条。后半 <code>/refactor</code> Skill 的安全协议,基本就是把它们逐条机械化了一遍。</p><h2 id="23-4-Fowler-的目录:坏味道与手法"><a href="#23-4-Fowler-的目录:坏味道与手法" class="headerlink" title="23.4 Fowler 的目录:坏味道与手法"></a>23.4 Fowler 的目录:坏味道与手法</h2><p>refactoring.guru 把 Fowler 书里的内容编成了两张能检索的目录:</p><ul><li>**代码坏味道(Code Smells)**是问题的征兆,好发现,但它可能只是更深层问题露出的一角。</li><li>**重构手法(Refactoring Techniques)**是具体的改法。每种手法都有得有失,所以每次重构都该有明确的动机,谨慎施用。</li></ul><p>手法分六大类:组合方法(Composing Methods)、在对象间搬移特性(Moving Features Between Objects)、组织数据(Organizing Data)、简化条件表达式(Simplifying Conditional Expressions)、简化方法调用(Simplifying Method Calls)、处理泛化关系(Dealing with Generalization)。</p><p>整个流程是「症状到处方」:你先嗅到味道,比如「过长方法」或者「Feature Envy」,目录再告诉你该用哪几种手法去治。这套映射关系明确、能机械执行,也正是 AI Agent 接得住的地方。</p><h2 id="23-5-refactor:把-Fowler-的书变成一个-Skill"><a href="#23-5-refactor:把-Fowler-的书变成一个-Skill" class="headerlink" title="23.5 /refactor:把 Fowler 的书变成一个 Skill"></a>23.5 /refactor:把 Fowler 的书变成一个 Skill</h2><p>理论讲完,看看 <a href="https://goal.rpcx.io/index.html">goal workflow 套件</a>里的 <code>/refactor</code> 是怎么把这本书落成一个能调用的能力的。</p><p><code>/refactor</code> 基于 Fowler《重构》第 2 版的完整目录:识别坏味道,套用验证过的手法,在不动外部行为的前提下把代码改清爽。它在 goal workflow 主闭环(第 8 章)之外,是个 Bonus 技能。安装一行就够:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npx skills add smallnest/goal-workflow --skill refactor</span><br></pre></td></tr></table></figure><p>触发可以直接打 <code>/refactor</code>,也可以用自然语言或术语:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">重构:重构 UserManager 类,它太大了</span><br><span class="line">code smell:这个函数有 Feature Envy,修复它</span><br><span class="line">extract method:把这个长方法拆分成更小的函数</span><br></pre></td></tr></table></figure><h3 id="23-5-1-22-种坏味道(五大类别)"><a href="#23-5-1-22-种坏味道(五大类别)" class="headerlink" title="23.5.1 22 种坏味道(五大类别)"></a>23.5.1 22 种坏味道(五大类别)</h3><p>Skill 内置 22 种代码坏味道,按五大类组织,每种都关联到对应的重构手法:</p><table><thead><tr><th>类别</th><th>坏味道</th><th>主要重构手法</th></tr></thead><tbody><tr><td><strong>臃肿类</strong></td><td>过长方法</td><td>Extract Method, Replace Temp with Query</td></tr><tr><td></td><td>过大的类</td><td>Extract Class, Extract Subclass</td></tr><tr><td></td><td>基本类型偏执</td><td>Replace Data Value with Object</td></tr><tr><td></td><td>过长参数列表</td><td>Introduce Parameter Object</td></tr><tr><td></td><td>数据泥团</td><td>Extract Class</td></tr><tr><td><strong>OO 滥用类</strong></td><td>Switch 语句</td><td>Replace Conditional with Polymorphism</td></tr><tr><td></td><td>临时字段</td><td>Extract Class, Introduce Null Object</td></tr><tr><td></td><td>被拒绝的遗赠</td><td>Replace Inheritance with Delegation</td></tr><tr><td></td><td>异曲同工的类</td><td>Rename Method, Extract Superclass</td></tr><tr><td><strong>变更阻碍类</strong></td><td>发散式变化</td><td>Extract Class</td></tr><tr><td></td><td>霰弹式修改</td><td>Move Method, Move Field</td></tr><tr><td></td><td>平行继承体系</td><td>Move Method, Move Field</td></tr><tr><td><strong>冗余类</strong></td><td>注释(代码本应自说明)</td><td>Extract Method, Rename Variable</td></tr><tr><td></td><td>重复代码</td><td>Extract Method, Pull Up Method</td></tr><tr><td></td><td>冗赘类</td><td>Inline Class, Collapse Hierarchy</td></tr><tr><td></td><td>纯数据类</td><td>Move Method, Encapsulate Field</td></tr><tr><td></td><td>死代码</td><td>删除(Git 历史有记录)</td></tr><tr><td></td><td>夸夸其谈未来性</td><td>Inline Class, Remove Parameter</td></tr><tr><td><strong>耦合类</strong></td><td>依恋情节</td><td>Move Method</td></tr><tr><td></td><td>狎昵关系</td><td>Move Method, Move Field</td></tr><tr><td></td><td>消息链</td><td>Hide Delegate</td></tr><tr><td></td><td>中间人</td><td>Remove Middle Man</td></tr><tr><td></td><td>不完美的库类</td><td>Introduce Foreign Method</td></tr></tbody></table><h3 id="23-5-2-40-种重构手法(六大类)"><a href="#23-5-2-40-种重构手法(六大类)" class="headerlink" title="23.5.2 40+ 种重构手法(六大类)"></a>23.5.2 40+ 种重构手法(六大类)</h3><p><img src="/images/image-20260626095835777.png"></p><p>每种手法都附带机械步骤(mechanics)和 before/after 对比示例,这正是 refactoring.guru 那张目录的 AI 可执行版:</p><table><thead><tr><th>类别</th><th>手法数</th><th>代表性手法</th></tr></thead><tbody><tr><td>组合方法</td><td>9</td><td>Extract Method, Inline Method, Extract Variable, Replace Temp with Query, Substitute Algorithm</td></tr><tr><td>移动特性</td><td>7</td><td>Move Method, Move Field, Extract Class, Inline Class, Hide Delegate</td></tr><tr><td>组织数据</td><td>13</td><td>Replace Data Value with Object, Encapsulate Field, Replace Type Code with Subclasses, Replace Magic Number</td></tr><tr><td>简化条件</td><td>8</td><td>Decompose Conditional, Guard Clauses, Replace Conditional with Polymorphism, Introduce Null Object</td></tr><tr><td>方法调用</td><td>13</td><td>Rename Method, Separate Query from Modifier, Introduce Parameter Object, Replace Error Code with Exception</td></tr><tr><td>泛化</td><td>9</td><td>Pull Up Method, Push Down Method, Extract Interface, Form Template Method, Replace Inheritance with Delegation</td></tr></tbody></table><h3 id="23-5-3-五阶段安全协议:把「做对了」清单制度化"><a href="#23-5-3-五阶段安全协议:把「做对了」清单制度化" class="headerlink" title="23.5.3 五阶段安全协议:把「做对了」清单制度化"></a>23.5.3 五阶段安全协议:把「做对了」清单制度化</h3><p><code>/refactor</code> 真正关键的设计不是目录,而是<strong>安全协议</strong>。它把 23.3 节那三条清单变成了 Agent 必须照办的执行护栏:</p><ul><li><strong>准备阶段</strong>:写特征测试(characterization test),提交当前状态,开一个重构分支。</li><li><strong>每一步</strong>:一次只改一处,编译过,测试全过,提交。</li><li><strong>验证阶段</strong>:测试全过,手动冒烟测一遍,自己审 diff,最后提交。</li><li><strong>铁律</strong>:不动外部行为,不夹带功能改动,每一步都有测试兜底。</li></ul><p>对着 Fowler 的三条清单看,正好一一对上:「代码必须变干净」对应自审 diff,「不写新功能」对应不夹带功能改动,「测试必须全过」对应每步测试加特征测试兜底。</p><p>AI 时代的价值就在这。人在 deadline 底下,难免偷偷给自己找台阶,「这块以后再重构」「这次先跳过测试」。Agent 被钉死在协议上,没这个空子可钻。重构的纪律,从「靠人自觉」变成了「靠机器强制」。</p><h3 id="23-5-4-语言专属指南"><a href="#23-5-4-语言专属指南" class="headerlink" title="23.5.4 语言专属指南"></a>23.5.4 语言专属指南</h3><p>Skill 还针对主流语言给出特化建议:</p><table><thead><tr><th>语言</th><th>核心建议</th></tr></thead><tbody><tr><td>Java</td><td>final 局部变量、IDE 自动重构、Records、Sealed Classes</td></tr><tr><td>TypeScript</td><td>解构减少参数、const 优先、Union Types 替代类型码、<code>?.</code> 消除 null 检查</td></tr><tr><td>Python</td><td>Type Hints、dataclasses、<code>@property</code>、Context Managers</td></tr><tr><td>Go</td><td>小接口、命名返回值、表格驱动测试、early returns 消除嵌套</td></tr><tr><td>Rust</td><td>Result/Option 替代错误码和 null、Pattern Matching、From trait、Derive macros</td></tr></tbody></table><h3 id="23-5-5-在工作流中的位置"><a href="#23-5-5-在工作流中的位置" class="headerlink" title="23.5.5 在工作流中的位置"></a>23.5.5 在工作流中的位置</h3><p><code>/refactor</code> 不单干,它跟两个邻居配合,其中最重要的是下一节要单独讲的 <code>/smell</code>;另外 <code>/modern-go</code>(35+ 条 gofix 风格规则)跟它一样,都是用来保持代码库健康的工具。还有 <code>/review-it</code>——它的审查原则里写得很清楚:「Reject noise,拒绝不切实际的边界情况、投机性风险、过度重构」。这跟 <code>/refactor</code> 安全协议里「小修复优先」是一个意思——重构不是越多越好,闻到坏味道才动手。</p><h2 id="23-6-smell:先诊断,再开刀"><a href="#23-6-smell:先诊断,再开刀" class="headerlink" title="23.6 /smell:先诊断,再开刀"></a>23.6 /smell:先诊断,再开刀</h2><p><code>/refactor</code> 解决的是"知道哪里该改、怎么改"。但更常见的困境在前一步:面对一个 AI 攒出来的几万行代码库,你不知道该从哪下手。哪个模块烂得最厉害?是架构错了,还是只有某个函数太长?先动哪块?</p><p><code>/smell</code> 回答的就是这个。它跟 <code>/refactor</code> 同级,分工是:<code>/smell</code> 诊断,扫出代码库的病灶并排优先级;<code>/refactor</code> 治疗,照 Fowler 手法一处处修。一个出报告,一个动手术。</p><p>安装:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npx skills add smallnest/goal-workflow --skill smell</span><br></pre></td></tr></table></figure><p>触发同样可以直接打 <code>/smell</code>,或用自然语言:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">代码坏味道检测:找出代码坏味道</span><br><span class="line">架构审计:检测架构反模式</span><br><span class="line">复杂度扫描:分析代码复杂度</span><br></pre></td></tr></table></figure><h3 id="23-6-1-比-Fowler-更宽的视野:8-大类-50-坏味道"><a href="#23-6-1-比-Fowler-更宽的视野:8-大类-50-坏味道" class="headerlink" title="23.6.1 比 Fowler 更宽的视野:8 大类 50+ 坏味道"></a>23.6.1 比 Fowler 更宽的视野:8 大类 50+ 坏味道</h3><p>这里要留意 <code>/smell</code> 和 <code>/refactor</code> 在覆盖面上的区别。<code>/refactor</code> 盯的是 Fowler 那 22 种坏味道,基本都在函数和类这个尺度。<code>/smell</code> 往上抬了一层,把架构和复杂度也算进来,一共 8 大类、50+ 种:</p><table><thead><tr><th>类别</th><th>示例</th></tr></thead><tbody><tr><td>架构</td><td>大泥球、分布式单体、贫血模型、CQRS 滥用、层边界违反</td></tr><tr><td>耦合</td><td>循环依赖、内容耦合、公共耦合(全局状态)、印记耦合</td></tr><tr><td>内聚</td><td>上帝对象、霰弹式修改、依恋情结、数据泥团</td></tr><tr><td>设计</td><td>抽象泄露、静态粘连、服务定位器滥用、SOLID 违反</td></tr><tr><td>代码</td><td>重复代码、长方法、基本类型偏执、魔数、死代码</td></tr><tr><td>测试</td><td>零测试覆盖、测试-实现耦合、不稳定测试</td></tr><tr><td>命名</td><td>模糊命名、命名不一致</td></tr><tr><td>复杂度</td><td>嵌套循环 (O(n²))、N+1 查询、重复线性扫描、循环内排序、渲染重复计算</td></tr></tbody></table><p>最后两类,「测试」和「复杂度」,是 Fowler 的坏味道目录里没有的,而它们在 AI 时代恰好用得上。AI 生成的代码经常零测试覆盖,也经常埋着 N+1 查询、循环内排序这类一眼看不出、上了量才爆的性能坑。这些 <code>/smell</code> 能一并扫出来。</p><h3 id="23-6-2-输出:一份带优先级的重构路线图"><a href="#23-6-2-输出:一份带优先级的重构路线图" class="headerlink" title="23.6.2 输出:一份带优先级的重构路线图"></a>23.6.2 输出:一份带优先级的重构路线图</h3><p><code>/smell</code> 不只是列问题,它产出一份结构化的 Markdown 报告:</p><ul><li>执行摘要和整体健康评估</li><li>检测到的架构风格,对比它"应该是"的风格</li><li>按严重级别分类的发现:严重 / 警告 / 建议</li><li>依赖图分析和模块健康评分卡</li><li>8 大类坏味道的分布统计</li><li>一份重构路线图,分成「立即可做 / 短期 / 长期」</li></ul><p>最有价值的是最后那份路线图。它直接回答了开头那个问题:该先动哪块。你可以让它只跑「仅严重」模式做快速体检,也可以把范围缩到某个模块,或者只扫最近改动的文件。</p><h3 id="23-6-3-两个-Skill-怎么配合"><a href="#23-6-3-两个-Skill-怎么配合" class="headerlink" title="23.6.3 两个 Skill 怎么配合"></a>23.6.3 两个 Skill 怎么配合</h3><p><img src="/images/image-20260626100640873.png"></p><p>把 <code>/smell</code> 和 <code>/refactor</code> 串起来,就是一条完整的"健康维护"链路:</p><ol><li><strong><code>/smell</code> 出报告</strong>:定位病灶,排好优先级,知道先改哪、后改哪。</li><li><strong><code>/refactor</code> 逐条施工</strong>:针对报告里的具体坏味道,套 Fowler 手法,走五阶段安全协议,每步提交。</li><li>改完再跑一遍 <code>/smell</code>,看健康评分有没有真的涨上去。</li></ol><p>这正好呼应了 23.3 节那条铁律,「代码必须变干净」。<code>/smell</code> 的健康评分让"变干净"这件事从主观感受变成了可量化的前后对比:重构前一个分,重构后一个分,涨了才算没白干。</p><h2 id="23-7-小结"><a href="#23-7-小结" class="headerlink" title="23.7 小结"></a>23.7 小结</h2><p>重构的方法论二十年没怎么变:嗅坏味道、对着手法目录、小步施工、每步测试、不夹带功能。AI 时代变的只有一件事,把 Fowler 那本书从「人读的参考书」变成了「Agent 执行的 Skill」,再用安全协议把人最容易偷的懒,改成机器必须遵守的护栏。goal workflow 把这件事拆成两个 Skill:<code>/smell</code> 诊断,扫出整个代码库的病灶并排好优先级;<code>/refactor</code> 治疗,照着 Fowler 手法一处处修。先体检,再开刀,改完再体检,这就是 AI 时代维护代码库健康的闭环。</p><p>第 22 章用 UML 让你读懂 AI 写的代码,这一章用重构让你改好 AI 写的代码。读懂是前提,改好是落点。两件事合起来,才是「你可以外包思考,但不能外包理解」这句话在工程上真正站住脚的样子。</p>
同分类推荐文章
- Goal Workflow:目标驱动的研发闭环 (2026-06-28 11:30:00)
- 科技爱好者周刊(第 401 期):如何赚到10亿美元 (2026-06-26 08:05:38)
- 如何做决策 - 从 Go 的一个 issue 说起 (2026-06-26 08:00:00)
建议继续学习
- 哪本书是对程序员最有影响、每个程序员都该阅读的书? (累计阅读 15,119)
- 在西方的程序员眼里,东方的程序员是什么样的? (累计阅读 9,919)
- PHP编码规范 (累计阅读 5,624)
- 为什么优秀的程序员既懒又笨 (累计阅读 4,997)
- 弱爆程序员的特征值 (累计阅读 4,616)
- 给你的代码《约法四章》:基本功能、错误处理、智能纠错、日志收集 (累计阅读 4,511)
- 实践中的重构 (累计阅读 4,003)
- 前端代码之丑(一):分支化技巧 (累计阅读 3,644)
- 重构发现:指针操作问题 (累计阅读 3,613)
- 从 if else 到 switch case 再到抽象 (累计阅读 3,401)