跳转到内容

代码重构流程

重构是程序员最爱的活,也是 AI 最容易翻车的活。

翻车的原因不是它不会改,而是它改着改着就改了行为。你让它把回调改成 async/await,它顺手把错误处理逻辑也「优化」了;你让它抽个函数,它顺便改了返回值。重构的重构是「在不改变外部行为的前提下改善内部结构」——这八个字,AI 不盯着就会跑偏。

下面这套流程,是让 Claude 老老实实做重构、不夹带私货的标准配方。

重构的第一步不是写代码,是写计划。进 Plan Mode,把方案先定下来:

refactor the authentication module to use async/await instead of callbacks. don't change any behavior. plan first

plan first 这三个字让 Claude 进入「先想后做」模式:它会列出当前回调结构目标 async/await 结构每一步怎么动每步如何验证,但不真的动文件。

Plan 出来之后,审计它。看看:

  • 步骤是不是真的「小步」?(每步可独立验证)
  • 有没有顺手夹带「优化逻辑」?(这就不是重构了)
  • 测试如何介入?(每步跑测试还是只最后跑)

Plan 不满意,让它重做。Plan 阶段改方案零成本,落到代码上就贵了。

Martin Fowler 在《重构》里讲的核心原则,在 AI 时代依然有效:小步走,每步绿

execute step 1 of the refactor plan. run the test suite. if green, commit with message "refactor: step 1 - extract callback into async function". if red, /rewind and tell me what went wrong

这种 prompt 把「一步」拆成五件事:执行、测试、判断、提交、回退。每一步都有出口,每一步都不让坏状态累积。

为什么不一次改完?因为重构一旦横跨多个文件、多轮 commit,出错的半径会指数放大。一步错,整个 diff 都得回退。小步走,最坏只回退一步。

每完成一组重构,让一个独立上下文的 review 子代理审一遍:

delegate to the review subagent: examine the last 3 refactor commits. confirm no behavior change. flag any place where logic was altered, not just structure

子代理的好处是它没看过你的对话历史——它只看代码本身,从外部视角判断「这是不是纯重构」。这比主代理自审更客观,因为它不会带着「我刚才为什么这么改」的偏见。

review 子代理返回的常见信号:

  • 「这里把 try/catch 改成了 .catch(),行为等价但错误堆栈不同」——可能是行为变更。
  • 「这里把同步函数改成了异步,调用方的执行时机变了」——肯定是行为变更。
  • 「这个函数被抽出来,参数顺序变了」——API 变了,不是重构。

任何一条命中,立刻 /rewind 回到上一步,重新来。

重构是 /rewind 用得最频繁的场景。因为重构是探索性的——你不知道这条路走得通走不通,得走了才知道。

/rewind 让你:

  • 试错免费:走错路,啪啪两下 Esc 回到上个检查点。
  • 多方案对比:方案 A 重构完跑一遍测试,rewind,方案 B 重构完跑一遍,对比哪个更干净。
  • 回退对话也回退代码:rewind 选项里选「两者都」,回到那个时刻从头来。

不用 /rewind 的重构,相当于徒手攀岩——一失手就摔到下面,捡不回来。用了它,相当于系了安全绳——你可以放手尝试,反正绳子拽着。

区分重构 vs 行为变更:最重要的一条

Section titled “区分重构 vs 行为变更:最重要的一条”

这一条单独拎出来,因为它是 AI 重构翻车的头号原因。

重构:在不改变外部行为的前提下,改善内部结构。 行为变更:改变了外部可观察的行为(输入相同但输出不同、错误处理不同、性能特性不同)。

AI 经常把这两件事混在一起做。比如你让它「抽个函数」,它顺手把「if x 改成 if x is not None」——逻辑更严谨了,但对 x = [] 这种边界,行为变了。

对抗方法:把「不允许行为变更」明确写进 prompt:

this is a pure refactor. do NOT:
- change error handling logic
- add or remove null checks
- change the order of operations
- "optimize" anything
if you spot a bug, flag it as a separate TODO. don't fix it inline.

这条 prompt 把「夹带私货」的可能性几乎堵死。Claude 看到 bug,只能记 TODO,不能动手——这就是「重构就是重构」的纪律。

Refactor target: src/auth/refresh.ts — convert from callback-based to async/await.
Plan first. The plan must:
1. list every function to change, in dependency order
2. specify how each step will be verified (which tests must pass)
3. explicitly mark any behavior change as OUT OF SCOPE — flag it, don't fix it
Constraints:
- public API signatures stay the same
- error messages stay the same
- no new dependencies
After each step:
- run npm test
- commit with conventional commit message "refactor(auth): step N - <what>"
- if any test fails, /rewind and report
When done, delegate to the review subagent to verify no behavior change.

填好这个模板,你的重构成功率会从「看运气」变成「大概率成」。