Skip to content

重构

大范围重构的分块策略、如何验证不破坏行为。

重构是 Claude Code 最容易翻车的场景,没有之一。它能读遍全项目、能一键批量改,这既是它的能力,也是它的陷阱。一口气吃掉整个重构,最后要么改坏一堆边界情况,要么改到一半上下文炸掉,你连回滚都不知道回到哪里。

这一节讲怎么把大范围重构拆成安全的分块步骤,全程可控、可验证、可回滚。

千万别一口气吃掉

先立一条规矩:任何涉及超过 10 个文件的重构,都要拆成多个 commit,每个 commit 独立跑通测试。

“一次搞完”在小项目上偶尔能成,在稍大点的项目上必翻。原因是:

  • 上下文窗口会用尽。改到中途 Claude Code 忘了前面的约定。
  • 中间某个 diff 有 bug 时,你不知道该看哪一部分,也无法二分定位。
  • 一旦要回滚,只能整个 revert。已经写对的部分白改。

分块的粒度以“每一块改完能独立跑通测试”为准。太粗测试通不过、太细上下文切换成本高。经验值:一次改一个模块或一类文件。

三阶段:map 影响面 → 分模块小步走 → 每步跑测试

阶段一:map 影响面

动手前先摸底。让 Claude Code 用只读的方式产出一份影响面清单:涉及多少文件、多少 call site、有哪些边界情况需要额外注意。

text
你: 我打算把项目里所有 moment.js 的调用换成 date-fns。
先别改代码,帮我 map 一下影响面,产出一个清单。

Claude Code:
[Grep "moment" --type ts]
[Grep "from ['\"]moment['\"]" --type ts]
[Read package.json]

影响面清单:

- 依赖:package.json 里 moment ^2.29.4,moment-timezone ^0.5.43
- 调用点:47 个文件,共 189 处 moment() 调用
- 主要用法:
  A. moment().format(...) — 82 处 (格式化)
  B. moment(x).add/subtract — 41 处 (时间加减)
  C. moment().isBefore/isAfter — 28 处 (比较)
  D. moment.duration — 19 处 (时长)
  E. moment-timezone — 19 处 (时区)
- 边界情况:
  - src/reports/monthly.ts 用了 moment 的 locale('zh-cn'),
    date-fns 的 locale 导入方式不同
  - src/utils/relative-time.ts 用了 fromNow(),
    date-fns 对应 formatDistanceToNow

建议分五批走,一批一个用法:
1. format
2. add/subtract
3. isBefore/isAfter
4. duration
5. moment-timezone → date-fns-tz

有了这份清单,你和 Claude Code 才知道自己面对什么。

阶段二:分模块小步走

按清单一批一批改。每一批走的是同一个循环:改代码 → 跑测试 → commit。

text
你: 第一批:把所有 .format(...) 的调用换成 date-fns 的 format。
只改这一批,别碰其他用法。改完跑 npm test。

Claude Code:
[Grep] 找到 82 处
[Edit src/utils/date.ts]
[Edit src/reports/daily.ts]
... (逐个改)
[npm test] ✓ 全绿

[git add -A]
[git commit -m "refactor(date): 迁移 82 处 moment format 到 date-fns"]

第一批完成。要不要继续第二批?

每一批都要 commit

每一批 commit 一次是硬要求。如果第三批发现问题,git bisect 或者 git revert 都有明确的目标 commit。不 commit 就等于把所有蛋放一个篮子。

阶段三:每步跑测试

“每步跑测试”不是走过场,是重构安全网的核心。

  • 有测试覆盖的模块:npm test 或者对应目录的测试集。
  • 测试覆盖不够的模块:先补测试再重构。不要边重构边补测试,两个目标搞不清哪个错了哪个对了。
  • 有 e2e 的:改完关键模块跑一遍 e2e。

如果某一批改完测试挂了,不要让 Claude Code 接着改别的。让它先分析测试挂的原因、修复、再走下一批。

text
你: 测试挂了 3 个。别继续第二批,先分析这 3 个是什么问题。

Claude Code:
[npm test 2>&1 | head -50]

3 个失败都在 monthly.ts。原因是 moment.format('MMMM') 输出全月份名
(January),但 date-fns 的 format 需要 pass locale 才有本地化名称。
默认 locale 是 en-US,我在改的时候忘了保留原本的 locale('zh-cn')。

修复方案:在 monthly.ts 里 import { zhCN } from 'date-fns/locale',
调用时 format(date, 'MMMM', { locale: zhCN })。

改吗?

修复完再往下走。这是重构不翻车的关键节奏。

用 subagent 做只读的 impact 分析

大范围重构里有一个常被忽略的技巧:用 subagent 做只读分析,主 loop 只做具体修改。

Claude Code 支持在会话中派出 subagent(参见 6.2 Subagent)。让 subagent 去做“找所有 call site”“分析边界情况”这类只读工作,好处是:

  • subagent 有独立的上下文窗口,不占用主 loop 的额度
  • 主 loop 只拿到 subagent 汇总后的结论,上下文清爽
  • 你可以并行派几个 subagent 同时看不同模块
text
你: 派 3 个 subagent 分别调研 src/reports、src/api、src/jobs 这三块
里 moment 的使用情况,各自出一份清单。别改代码。

Claude Code:
[派 subagent A → 分析 src/reports]
[派 subagent B → 分析 src/api]
[派 subagent C → 分析 src/jobs]

[汇总 3 份报告]
reports:  22 处,主要是 format 和 duration
api:      35 处,全是 format,无边界情况
jobs:     58 处,含 12 处 timezone 相关,需要谨慎

建议先做 api(最简单),再做 reports,最后 jobs。

这种分工在重构 5 万行以上的仓库时几乎是必需品。

完整案例:moment.js 换 date-fns

假设你决定把项目里所有 moment 换掉。整个流程展开是这样:

第 1 步 — 建分支:

text
你: 从 main 拉一个分支 refactor/moment-to-date-fns,先别改代码。

第 2 步 — map 影响面(如上): 得到 5 类用法、47 个文件、189 处调用的清单。

第 3 步 — 装 date-fns,先不删 moment:

text
你: 把 date-fns 装上(不要删 moment),加一个 CHANGELOG 说明这次
迁移的动机和分批计划。commit。

保留旧依赖是重要的安全网。整个迁移过程中,两个库并存,直到最后一步才移除 moment。

第 4 步 — 分 5 批改:

text
你: 第一批:迁移所有 format 用法。跑测试后 commit。

改完 commit。第二批到第五批依次同样打法。

第 5 步 — 确认 moment 已无引用:

text
你: grep 一遍确认没有 moment 的 import 了。有的话告诉我在哪。

Claude Code:
[Grep "from ['\"]moment"]  → 无匹配
[Grep "require\\(['\"]moment"] → 无匹配

已清干净。可以从 package.json 移除 moment 和 moment-timezone。

第 6 步 — 移除依赖,跑全测:

text
你: 从 package.json 移除 moment 和 moment-timezone,删 node_modules
重装,跑全测。

Claude Code:
[Edit package.json]
[rm -rf node_modules; npm install]
[npm test] ✓ 全绿
[npm run build] ✓

搞定。要发 PR 吗?

整个流程下来,PR 里是 7 个清晰的 commit,每个 commit 独立通过 CI,reviewer 也能一段一段审。这是重构该有的样子。

什么时候可以“小步走”放宽

如果重构范围是一个文件内部(比如把一个 300 行的 class 拆成 3 个 class),不需要走 map 影响面这一套。直接让 Claude Code 一次改完,跑测试,commit 就行。

分块策略是针对跨文件、跨模块的重构。文件内重构该快就快。

本教程为社区中文学习整理,非官方发布。Claude Code 属于 Anthropic。