重构:AI 时代的代码进化

目录 [−]

  1. 23.1 什么是重构,什么是技术债
  2. 23.2 何时重构:三次法则
  3. 23.3 如何重构:正确的姿势
  4. 23.4 Fowler 的目录:坏味道与手法
  5. 23.5 /refactor:把 Fowler 的书变成一个 Skill
    1. 23.5.1 22 种坏味道(五大类别)
    2. 23.5.2 40+ 种重构手法(六大类)
    3. 23.5.3 五阶段安全协议:把「做对了」清单制度化
    4. 23.5.4 语言专属指南
    5. 23.5.5 在工作流中的位置
  6. 23.6 /smell:先诊断,再开刀
    1. 23.6.1 比 Fowler 更宽的视野:8 大类 50+ 坏味道
    2. 23.6.2 输出:一份带优先级的重构路线图
    3. 23.6.3 两个 Skill 怎么配合
  7. 23.7 小结

「Any fool can write code that a computer can understand. Good programmers write code that humans can understand.」
任何傻瓜都能写出计算机能看懂的代码。好程序员写的是人能看懂的代码。
—— Martin Fowler

第 22 章解决了「人怎么读懂 AI 写的代码」,用 UML 把代码画成图。

读懂之后呢?你打开 AI 生成的代码,能跑,但是一团乱麻:一个方法三百行,一个类管了八件事,同样的逻辑复制了五遍。这时候怎么办?

重构。

这件事本身不新鲜,Martin Fowler 1999 年就把它写成了一本书。变的是执行者。以前是人对着那本书一处一处手动改,现在是 AI 对着同一本书的目录自动改,人退到后面审查 diff。

本章分两半。前半讲重构的理论根基:什么是重构、技术债是怎么回事、什么时候该重构、Fowler 编了哪些坏味道和手法。这部分主要参考 Fowler 的《重构》,以及把这本书做成在线可检索版本的 refactoring.guru。后半讲 goal workflow 套件里的两个 Skill:/refactor(介绍页:https://goal.rpcx.io/index_cn.html#step-refactor )把 Fowler 第 2 版的整个目录封进了一个 AI Agent 能直接调用的能力里;/smell(介绍页:https://goal.rpcx.io/index_cn.html#step-smell )则在它前面一步,负责扫出整个代码库到底哪里有问题。一个诊断,一个治疗。

23.1 什么是重构,什么是技术债

重构(refactoring)是在不改变外部行为的前提下改善代码内部结构。说白了,把一团乱麻(dirty code)整理成清爽的代码(clean code),但程序对外的表现一字不变。

  • 脏代码是经验不足,再加上 deadline、管理混乱、开发途中走捷径,几样凑一起的产物。
  • 整洁代码易读、易懂、易维护,它让开发变得可预测。你大概知道改一处要花多久,而不是每次都掉进未知的坑。

代码为什么会从整洁滑向肮脏?Ward Cunningham 的技术债这个说法解释得很到位。走捷径、跳过测试,就像找银行贷款:眼下买东西是快了,但要付利息。你不光还本金,还得还利息;利息攒够了,甚至会超过你的全部收入,永远还不清。代码也一样。不写测试能暂时提速,可它每天都在拖你后腿,直到哪天你补上测试,把债还掉。

refactoring.guru 列了一串技术债的成因。这些在 AI 时代值得重读,因为大多被放大了:

  • 业务压力:功能没做完就得上线,于是补丁和权宜之计堆上去,盖住没收尾的部分。
  • 看不到后果:管理层不知道技术债有「利息」,债越欠越多、开发越来越慢,于是舍不得给重构留时间。
  • 强耦合没人管:项目变成一块铁板而不是一堆独立模块,动一处牵全身。
  • 缺测试:没有即时反馈,人就敢用又快又险的 workaround。最坏的情况是没测试直接上线,可能给几千个客户发出一封诡异的测试邮件,也可能直接把数据库清空。
  • 缺文档、缺沟通:新人上手慢;关键的人一走,开发就停摆。
  • 长期多分支并行:隔离的改动越多,合并时积下的债越大。
  • 重构一拖再拖:需求一直变,旧代码总有显得笨重的一天;可程序员每天还在往旧代码上接新代码,所以拖得越久,将来要返工的依赖就越多。

「缺测试」和「重构一拖再拖」这两条在 AI 时代尤其要命。Agent 几分钟就能吐出几千行代码,没有特征测试兜底、没有及时整理,技术债的本金和利息会以人类时代见不到的速度滚起来。第 12 章讲 GSD Core 时提过的「反合理化表」,对抗的正是「以后再重构」「先跳过测试」这类自欺。它其实就是技术债成因的制度化解药。

23.2 何时重构:三次法则

Fowler 的经验法则很简洁——三次法则(Rule of Three)

  1. 第一次做某件事,直接做完。
  2. 第二次做类似的事,心里别扭也照旧重复一遍。
  3. 第三次再碰到同类的事,开始重构。

第一次重复忍着,第三次出现就动手。除此之外还有三个天然时机:

  • 加功能时:重构能帮你读懂别人的代码。面对一坨脏代码,先重构再加,整洁的代码好改得多。这一改不只方便你,也方便后面接手的人。
  • 修 bug 时:bug 跟现实里的虫子一样,专挑代码最暗最脏的角落待着。把代码清干净,错误几乎会自己冒出来。
  • 代码评审时:这往往是代码公开前最后一次整理的机会。最好跟作者结对来做,简单问题当场修掉,复杂的估个时间再说。

23.3 如何重构:正确的姿势

重构是一连串小改动,每一步让代码好一点,同时让程序始终能跑。refactoring.guru 的「做对了」清单只有三条,却是整套方法论的核心:

  • 代码必须变干净。 重构完还是一团乱,那这一小时就白花了。这种事多半发生在你丢掉「小步改」、把一大堆重构混进一次大改动的时候。尤其赶 deadline 的时候,很容易把自己绕晕。
  • 重构期间不写新功能。 别把重构和加特性搅在一起,至少在单个 commit 里把两件事分开。
  • 重构后所有已有测试都得过。 测试挂了只有两种可能:要么你改错了,修就是;要么你的测试太底层了,比如去测了类的私有方法,这种情况是测试的锅,该把它重写成更高层、BDD 风格的测试。

记住这三条。后半 /refactor Skill 的安全协议,基本就是把它们逐条机械化了一遍。

23.4 Fowler 的目录:坏味道与手法

refactoring.guru 把 Fowler 书里的内容编成了两张能检索的目录:

  • **代码坏味道(Code Smells)**是问题的征兆,好发现,但它可能只是更深层问题露出的一角。
  • **重构手法(Refactoring Techniques)**是具体的改法。每种手法都有得有失,所以每次重构都该有明确的动机,谨慎施用。

手法分六大类:组合方法(Composing Methods)、在对象间搬移特性(Moving Features Between Objects)、组织数据(Organizing Data)、简化条件表达式(Simplifying Conditional Expressions)、简化方法调用(Simplifying Method Calls)、处理泛化关系(Dealing with Generalization)。

整个流程是「症状到处方」:你先嗅到味道,比如「过长方法」或者「Feature Envy」,目录再告诉你该用哪几种手法去治。这套映射关系明确、能机械执行,也正是 AI Agent 接得住的地方。

23.5 /refactor:把 Fowler 的书变成一个 Skill

理论讲完,看看 goal workflow 套件里的 /refactor 是怎么把这本书落成一个能调用的能力的。

/refactor 基于 Fowler《重构》第 2 版的完整目录:识别坏味道,套用验证过的手法,在不动外部行为的前提下把代码改清爽。它在 goal workflow 主闭环(第 8 章)之外,是个 Bonus 技能。安装一行就够:

1
npx skills add smallnest/goal-workflow --skill refactor

触发可以直接打 /refactor,也可以用自然语言或术语:

1
2
3
重构:重构 UserManager 类,它太大了
code smell:这个函数有 Feature Envy,修复它
extract method:把这个长方法拆分成更小的函数

23.5.1 22 种坏味道(五大类别)

Skill 内置 22 种代码坏味道,按五大类组织,每种都关联到对应的重构手法:

类别 坏味道 主要重构手法
臃肿类 过长方法 Extract Method, Replace Temp with Query
过大的类 Extract Class, Extract Subclass
基本类型偏执 Replace Data Value with Object
过长参数列表 Introduce Parameter Object
数据泥团 Extract Class
OO 滥用类 Switch 语句 Replace Conditional with Polymorphism
临时字段 Extract Class, Introduce Null Object
被拒绝的遗赠 Replace Inheritance with Delegation
异曲同工的类 Rename Method, Extract Superclass
变更阻碍类 发散式变化 Extract Class
霰弹式修改 Move Method, Move Field
平行继承体系 Move Method, Move Field
冗余类 注释(代码本应自说明) Extract Method, Rename Variable
重复代码 Extract Method, Pull Up Method
冗赘类 Inline Class, Collapse Hierarchy
纯数据类 Move Method, Encapsulate Field
死代码 删除(Git 历史有记录)
夸夸其谈未来性 Inline Class, Remove Parameter
耦合类 依恋情节 Move Method
狎昵关系 Move Method, Move Field
消息链 Hide Delegate
中间人 Remove Middle Man
不完美的库类 Introduce Foreign Method

23.5.2 40+ 种重构手法(六大类)

每种手法都附带机械步骤(mechanics)和 before/after 对比示例,这正是 refactoring.guru 那张目录的 AI 可执行版:

类别 手法数 代表性手法
组合方法 9 Extract Method, Inline Method, Extract Variable, Replace Temp with Query, Substitute Algorithm
移动特性 7 Move Method, Move Field, Extract Class, Inline Class, Hide Delegate
组织数据 13 Replace Data Value with Object, Encapsulate Field, Replace Type Code with Subclasses, Replace Magic Number
简化条件 8 Decompose Conditional, Guard Clauses, Replace Conditional with Polymorphism, Introduce Null Object
方法调用 13 Rename Method, Separate Query from Modifier, Introduce Parameter Object, Replace Error Code with Exception
泛化 9 Pull Up Method, Push Down Method, Extract Interface, Form Template Method, Replace Inheritance with Delegation

23.5.3 五阶段安全协议:把「做对了」清单制度化

/refactor 真正关键的设计不是目录,而是安全协议。它把 23.3 节那三条清单变成了 Agent 必须照办的执行护栏:

  • 准备阶段:写特征测试(characterization test),提交当前状态,开一个重构分支。
  • 每一步:一次只改一处,编译过,测试全过,提交。
  • 验证阶段:测试全过,手动冒烟测一遍,自己审 diff,最后提交。
  • 铁律:不动外部行为,不夹带功能改动,每一步都有测试兜底。

对着 Fowler 的三条清单看,正好一一对上:「代码必须变干净」对应自审 diff,「不写新功能」对应不夹带功能改动,「测试必须全过」对应每步测试加特征测试兜底。

AI 时代的价值就在这。人在 deadline 底下,难免偷偷给自己找台阶,「这块以后再重构」「这次先跳过测试」。Agent 被钉死在协议上,没这个空子可钻。重构的纪律,从「靠人自觉」变成了「靠机器强制」。

23.5.4 语言专属指南

Skill 还针对主流语言给出特化建议:

语言 核心建议
Java final 局部变量、IDE 自动重构、Records、Sealed Classes
TypeScript 解构减少参数、const 优先、Union Types 替代类型码、?. 消除 null 检查
Python Type Hints、dataclasses、@property、Context Managers
Go 小接口、命名返回值、表格驱动测试、early returns 消除嵌套
Rust Result/Option 替代错误码和 null、Pattern Matching、From trait、Derive macros

23.5.5 在工作流中的位置

/refactor 不单干,它跟两个邻居配合,其中最重要的是下一节要单独讲的 /smell;另外 /modern-go(35+ 条 gofix 风格规则)跟它一样,都是用来保持代码库健康的工具。还有 /review-it——它的审查原则里写得很清楚:「Reject noise,拒绝不切实际的边界情况、投机性风险、过度重构」。这跟 /refactor 安全协议里「小修复优先」是一个意思——重构不是越多越好,闻到坏味道才动手。

23.6 /smell:先诊断,再开刀

/refactor 解决的是"知道哪里该改、怎么改"。但更常见的困境在前一步:面对一个 AI 攒出来的几万行代码库,你不知道该从哪下手。哪个模块烂得最厉害?是架构错了,还是只有某个函数太长?先动哪块?

/smell 回答的就是这个。它跟 /refactor 同级,分工是:/smell 诊断,扫出代码库的病灶并排优先级;/refactor 治疗,照 Fowler 手法一处处修。一个出报告,一个动手术。

安装:

1
npx skills add smallnest/goal-workflow --skill smell

触发同样可以直接打 /smell,或用自然语言:

1
2
3
代码坏味道检测:找出代码坏味道
架构审计:检测架构反模式
复杂度扫描:分析代码复杂度

23.6.1 比 Fowler 更宽的视野:8 大类 50+ 坏味道

这里要留意 /smell/refactor 在覆盖面上的区别。/refactor 盯的是 Fowler 那 22 种坏味道,基本都在函数和类这个尺度。/smell 往上抬了一层,把架构和复杂度也算进来,一共 8 大类、50+ 种:

类别 示例
架构 大泥球、分布式单体、贫血模型、CQRS 滥用、层边界违反
耦合 循环依赖、内容耦合、公共耦合(全局状态)、印记耦合
内聚 上帝对象、霰弹式修改、依恋情结、数据泥团
设计 抽象泄露、静态粘连、服务定位器滥用、SOLID 违反
代码 重复代码、长方法、基本类型偏执、魔数、死代码
测试 零测试覆盖、测试-实现耦合、不稳定测试
命名 模糊命名、命名不一致
复杂度 嵌套循环 (O(n²))、N+1 查询、重复线性扫描、循环内排序、渲染重复计算

最后两类,「测试」和「复杂度」,是 Fowler 的坏味道目录里没有的,而它们在 AI 时代恰好用得上。AI 生成的代码经常零测试覆盖,也经常埋着 N+1 查询、循环内排序这类一眼看不出、上了量才爆的性能坑。这些 /smell 能一并扫出来。

23.6.2 输出:一份带优先级的重构路线图

/smell 不只是列问题,它产出一份结构化的 Markdown 报告:

  • 执行摘要和整体健康评估
  • 检测到的架构风格,对比它"应该是"的风格
  • 按严重级别分类的发现:严重 / 警告 / 建议
  • 依赖图分析和模块健康评分卡
  • 8 大类坏味道的分布统计
  • 一份重构路线图,分成「立即可做 / 短期 / 长期」

最有价值的是最后那份路线图。它直接回答了开头那个问题:该先动哪块。你可以让它只跑「仅严重」模式做快速体检,也可以把范围缩到某个模块,或者只扫最近改动的文件。

23.6.3 两个 Skill 怎么配合

/smell/refactor 串起来,就是一条完整的"健康维护"链路:

  1. /smell 出报告:定位病灶,排好优先级,知道先改哪、后改哪。
  2. /refactor 逐条施工:针对报告里的具体坏味道,套 Fowler 手法,走五阶段安全协议,每步提交。
  3. 改完再跑一遍 /smell,看健康评分有没有真的涨上去。

这正好呼应了 23.3 节那条铁律,「代码必须变干净」。/smell 的健康评分让"变干净"这件事从主观感受变成了可量化的前后对比:重构前一个分,重构后一个分,涨了才算没白干。

23.7 小结

重构的方法论二十年没怎么变:嗅坏味道、对着手法目录、小步施工、每步测试、不夹带功能。AI 时代变的只有一件事,把 Fowler 那本书从「人读的参考书」变成了「Agent 执行的 Skill」,再用安全协议把人最容易偷的懒,改成机器必须遵守的护栏。goal workflow 把这件事拆成两个 Skill:/smell 诊断,扫出整个代码库的病灶并排好优先级;/refactor 治疗,照着 Fowler 手法一处处修。先体检,再开刀,改完再体检,这就是 AI 时代维护代码库健康的闭环。

第 22 章用 UML 让你读懂 AI 写的代码,这一章用重构让你改好 AI 写的代码。读懂是前提,改好是落点。两件事合起来,才是「你可以外包思考,但不能外包理解」这句话在工程上真正站住脚的样子。