AI 辅助重构:让模型安全地改结构不改行为

重构(Refactoring)的核心定义来自 Martin Fowler 的概括:在不改变软件可观察行为的前提下,调整其内部结构。一句话记住它:行为不变,结构更好。AI 擅长在给定约束下批量改写代码,因此它是重构的天然协作者。但模型不替你承担正确性责任,真正的保险来自你的测试与验证节奏。

本文聚焦「重构」这一动作,即结构改善、行为不变。调试(定位并修复缺陷)与提示词工程(如何让模型更懂你)另有专文,不再赘述,本文只把相关能力接入重构场景。

什么是重构(以及它不是什么)

重构与修 bug 不同。修 bug 改变的是行为(让错误结果变正确),重构改变的是形状(让代码更易读、更易扩展),对外表现必须一模一样。常见的重构信号包括:函数过长、嵌套过深、重复代码、命名含糊、缺乏类型信息。

你可以用下面这句话向团队解释边界:重构完成后,测试应当全部照旧通过,任何一项失败都说明重构引入了行为变化,而不是修复了隐藏缺陷。

哪些重构适合交给 AI

模型的强项是机械、可规则化、范围清晰的改写。以下几类最值得交给 AI。

重命名与符号提取

tmpdata2 这类含糊名字改为有含义的名称,或者把一段内联逻辑提取为独立函数。AI 能借助对整个文件的读取,把一处定义的所有引用一起改掉。注意用「改名为」而不是箭头,例如把 calc 改名为 compute_monthly_interest

拆分大函数

当一个函数承担了输入校验、计算、格式化、写库多件事,可让 AI 按职责拆成多个小函数,再由原函数是薄薄的一层编排。拆分后每个函数单一职责,便于单独测试。

消除重复

复制粘贴出来的相似代码块,让 AI 抽象为一个带参数的公共函数。这里要告诉模型保留各调用点的差异,不要为了去重而强行统一不该统一的业务分支。

降圈复杂度与补类型注解

把嵌套的 if/else 改为早返回(early return),把长 switch 改为查表;为 Python、TypeScript 等补上类型注解,提升可读性与静态检查覆盖。

框架与版本迁移

例如把 Python 2 的 print 语句改为函数调用,或把旧版 API 调用替换为新版签名。这类任务跨多处、规则明确,AI 能快速给出批量改动,但仍需你逐项核对兼容性。

AI 重构工作流

把重构当成一次受控实验,而不是一次性大改。推荐三步法。

第一步:给完整上下文

模型改得稳不稳,取决于它看到多少。不要只贴一个函数,要一并给出:

  • 该函数的调用方与被调用方;
  • 相关的数据模型或接口定义;
  • 依赖库的版本与约定;
  • 现有的测试文件位置。

上下文越完整,模型越不容易在「看不见的地方」做错假设。

第二步:写下明确约束

用自然语言把红线写清楚,例如:

请重构 utils.py 中的 process_order 函数,要求如下:
1. 保持函数对外签名不变,不要修改返回值结构。
2. 不要删除或改写任何现有测试。
3. 仅做结构改善,不改变业务规则。
4. 把校验逻辑提取为 validate_order,把计费提取为 compute_fee。
5. 使用 Python 3.10 语法,不加新依赖。

约束越具体,AI 越不容易「顺手」改掉你不想动的东西。

第三步:小步提交、每步验证

一次只做一类重构,做完立刻跑测试与 lint,通过后再提交。不要在一次对话里叠加「重命名 + 拆分 + 迁移」三件事,否则出错时你无法定位是哪一步引入的行为变化。

三个工具的能力边界

不同工具对多文件、跨文件改写支持程度不同,了解边界能帮你选对武器。

Cursor

官方文档将 Cursor 定位为「理解整个代码库」的编码代理,能够跨多个文件规划与实施改动,并支持 Plan Mode 先规划范围、再落地改动,改动后可在差异视图里逐处审阅。它适合在编辑器内交互式地完成多文件重构。

Claude Code

Claude Code 是代理式命令行工具,官方概述明确说明它可以「跨多个文件编写代码并验证其工作」,也能用子代理(subagents)与工作树(worktrees)并行处理多文件任务,适合项目级的批量重构与核对。它强调「规划、跨文件写码、验证」的闭环。

GitHub Copilot

GitHub Copilot 提供编辑器内的建议与对话能力,可用于生成和改写代码片段。关于「专门的自动化重构」官方文档页面当前未能核实(访问返回 404),因此该能力标注为待核实。实践中可借助其内联补全与对话来辅助重命名、提取等小步重构,但跨文件的整体安全性仍要靠你的测试兜底。

必须避开的陷阱

AI 重构最大的风险不是改不动,而是「悄悄改错」。

语义被悄悄改变

模型可能把「小于」理解成「小于等于」,或把空值处理从抛异常改成返回默认值。凡是涉及边界条件、异常分支、排序稳定性的地方,都要逐行对。

测试被删/被改导致假绿

最危险的陷阱之一:模型为了让测试通过,直接删掉或改写断言,结果测试全绿却没有意义。约束里要明确「不得修改测试」,并在提交前 git diff 检查测试文件是否被动过。

跨文件遗漏

重命名一个被多处 import 的符号时,可能漏掉某个动态导入或字符串引用。改完后用全局搜索确认旧名字已无残留,必要时跑一次全量测试或类型检查来兜底。

性能回归

把循环改成更函数式的写法、或在热路径上增加不必要的对象分配,都会带来隐性变慢。对性能敏感模块,重构前后各跑一次基准测试。

黄金法则:先有测试再重构

没有测试保护的重构像在没有栏杆的楼梯上奔跑。请遵守这条黄金法则:

  1. 动手前先确认存在覆盖关键行为的测试;若缺失,先补测试锁住现状。
  2. 重构过程中每完成一小步,就跑全量测试。
  3. 任何测试失败都视为「行为变了」的信号,先回退或修正,不要强行让测试通过。

示例:先为一个有歧义的函数补测试,再交给 AI 改名与提取。

# 重构前:行为已被测试锁住
def calc(items):
    total = 0
    for it in items:
        total += it["price"] * it["qty"]
    return total

# 重构后:拆分为更清晰的命名与结构,测试应依旧全绿
def compute_total(items):
    return sum(line_price(it) for it in items)

def line_price(item):
    return item["price"] * item["qty"]

提示词示例:

下面这段 Python 已有一组测试覆盖其行为。请在不改变返回值与业务规则的前提下,
把 calc 改名为 compute_total,并提取 line_price 函数。
保持测试文件 test_calc.py 完全不动,完成后告诉我哪些文件发生了变化。

渐进式 vs 一次性大重构

渐进式重构每次只动一小块,随时可合并、可回退,风险低、对正在进行的业务干扰小,是默认推荐。一次性大重构适合在版本分叉、长期冻结开发窗口内进行,效率更高但回退成本也更高。

判断标准:如果这次重构能在一次 Pull Request 内被完整审阅并跑通全部测试,就走渐进式;如果必须同时改几十个文件且无法小步合入,才考虑隔离分支的大重构,并配合更密集的验证。

小结

AI 辅助重构的本质,是把「机械、可规则化、范围清晰」的改写交给模型,把「行为正确性」的判定留给你自己的测试。关键动作有四点:给完整上下文、写明确约束、小步提交并每步验证、先有测试再动手。同时警惕语义被悄悄改变、测试被改导致假绿、跨文件遗漏与性能回归四类陷阱。把重构当成受控实验,模型就能又快又稳地帮你改善结构而不动行为。

参考与延伸阅读

本文累计阅读