AI 重构代码 40% 会悄悄改坏,码哥字节用 7 步 SOP 防住
AI重构代码40%会悄悄改坏,我是怎么用7步SOP防住的 - 码哥字节
码哥字节复盘一次用 Claude Code 重构订单服务后线上 P99 延迟翻四倍的经历,提出所有 AI 参与的重构必须走完 7 步流程。这套 SOP 包括静态分析出问题清单、梳理运行时依赖、单函数粒度小步重构、先写测试再让 AI 改、用新鲜上下文做对抗性 Review、feature flag 灰度发布和 git tag 回滚预案。

上线第3天,监控告警群炸了。
不是新业务的问题,是我上周用 Claude Code 重构的那个订单服务。看起来一切正常,CI 全绿,单测跑过。但线上 P99 延迟翻了四倍,原因是 AI 在"优化"某个查询函数时,把一个 O(n) 的内存过滤替换成了每次请求都查数据库的子查询。
我当时盯着那个 diff,脑子里闪过的就是两个月前那篇踩坑帖里的经历。同样的剧本,换个时间地点又演了一遍。后来我给团队定的规矩是:所有 AI 参与的重构,必须走完 7 步流程,缺一不可。
这篇就是把这套 SOP 摊开讲。

为什么AI重构总在关键时刻掉链子
说白了,AI 重构翻车不是偶然。
第一,上下文窗口不够大。 Anthropic 官方就说过,上下文窗口是最重要的资源,性能随上下文填充而下降。你让它一次性看懂 50 个文件里跨文件的引用关系,它只会凭概率猜。Augment Code 实测过一个跨 50 个文件、200K token 级别的重构任务,40% 的结果出现了上下文漂移,也就是说 AI 改了一处,连带把无关模块的调用逻辑给改了。
第二,重复代码不降反升。 GitClear 2025 年的数据显示,AI 辅助开发后,团队的重复代码率从 8.3% 涨到了 12.3%。你以为 AI 帮你清理了烂代码,其实它在别的地方又塞了一批更隐蔽的重复。有意思的是,真正经过重构的代码比例反而降到了 10% 左右。说明 AI 生成的"优化"大部分只是表面改写,没触及结构问题。
第三,速度红利消失得极快。 多项研究的共识是,AI 辅助开发的初始提速能达到 3 到 5 倍。但两个月后这个优势基本归零。与此同时,30% 的项目出现静态分析告警上升,41% 的项目代码复杂度不降反升。METR 的一次实测更直接,让资深工程师用 AI 工具做重构任务,结果多花了 19% 的时间,而且 0% 的 PR 可以直接合进主分支。
AI 擅长局部改写,不擅长全局推理。 这个判断不是悲观,而是前提。承认这个前提,才能设计出真正能用的流程。

7步SOP总览
| 步骤 | 做什么 | 核心工具 | 输出物 |
|---|---|---|---|
| Step 1 | 静态分析,找出真问题 | Claude Code / SonarQube | 问题清单 |
| Step 2 | 梳理运行时依赖 | Claude Code + grep | 依赖图 |
| Step 3 | 小步重构,单函数粒度 | Claude Code | 单文件 diff |
| Step 4 | 先写测试再让 AI 改 | Claude Code / Jest | 测试用例 |
| Step 5 | 对抗性 Review | Claude Code(新鲜上下文) | Review 记录 |
| Step 6 | Feature flag + 灰度发布 | Feature Flag 平台 | 灰度配置 |
| Step 7 | 回滚预案 | git checkpoint | 一键回退脚本 |
这 7 步不是线性流水线。Step 3 和 Step 4 是一个循环:写好单测,让 AI 改,测不过就回到 Step 4,直到单测全部通过才进入 Step 5。
每一步都要有明确的可验证产出。 没有产出物的步骤,就是在浪费时间。
Step 1:静态分析,用 AI 扫出真问题
很多人一上来就让 AI 读整个仓库然后"帮我重构"。错了。第一步应该让 AI 当质检员,而不是施工队。
我的做法是用 Claude Code 对目标目录做一次结构化扫描,让它按严重等级分类,并给出每个问题的具体位置和修改建议。
@src/services/order-service.ts
@src/services/payment-service.ts
@src/utils/
你是一个代码审查专家。请依次完成以下任务:
1. 列出所有超过 50 行的函数,标注行号和文件
2. 找出重复代码片段(3行以上相同或高度相似的代码块),标注位置
3. 识别未使用的 import 和导出的接口
4. 标注所有 try-catch 吞异常的情况
5. 按优先级排序,只返回 Top 10 问题,格式:[P0/P1/P2] 文件:行号 → 问题描述 → 修改方向
不要给出完整代码。只需要诊断报告。
这一步的输出是一份问题清单,不是修改建议。有了清单,你才能判断哪些是真问题,哪些是 AI 在制造焦虑。
实际用起来你会发现,SonarQube 或者 ESLint 能扫出来的东西就别让 AI 扫。AI 的价值在于理解语义层面的问题,比如函数职责是否单一,命名是否准确反映意图。这些静态工具扫不到。
输出物:一份按优先级排序的问题清单,P0 不超过 5 个。

Step 2:依赖梳理,别动不该动的东西
这是我最常翻车的地方。改完本地测试全绿,一到生产环境就挂。原因通常是一个隐式依赖。
有些依赖不是你 import 的那些,比如全局缓存状态、配置文件读取、环境变量覆盖。这些东西光看代码看不出来,得确认它们怎么影响运行时行为。
@src/
帮我梳理 src/services/order-service.ts 的所有依赖关系:
1. 直接导入:列出所有 import 语句及其来源
2. 间接依赖:追踪第1步中引用的文件的 import,至少深入两层
3. 外部输入:列出该文件直接或间接读取的环境变量、配置文件路径、数据库表名
4. 副作用:标注所有对外部状态的写操作(缓存、日志、消息队列发送等)
5. 如果某个依赖只在特定条件下触发,请用注释标注触发条件
最后输出一份依赖树,根节点是 order-service.ts。
依赖梳理完了之后,你要心里有数:哪些文件可以动,哪些文件只能读不能碰。
这一步经常被跳过。我见过不少团队直接用 AI 批量改造,结果改完发现某个 helper 函数被 12 个文件引用,其中 3 个还在维护期,沟通成本比重构本身还高。
输出物:一份依赖清单,区分安全区和保护区。
Step 3:小步重构,单函数粒度
到了真正动手的环节。这时候最容易犯的错误就是一口气让 AI 改太多。
我的原则是一次只改一个函数。改完验证通过再做下一个。别贪多。
@src/services/order-service.ts::createOrder
我需要重构这个 createOrder 函数。它当前长度是 87 行,承担了过多职责。
约束条件:
1. 保持现有 public API 签名不变,包括参数类型和返回值
2. 提取公共逻辑为私有辅助函数
3. 每个新函数的行数不超过 40 行
4. 保留原有的错误处理模式
5. 不改变任何外部可观察的行为(日志输出、异常类型、HTTP状态码)
请先写出你的重构计划,等我确认后再生成代码。计划格式:
- 原函数拆分为哪几个新函数
- 每个新函数的职责一句话描述
- 是否有需要额外关注的边界情况
注意这里有一个关键细节:要求 AI 先出计划,等你确认再生成代码。 这不是多余的。很多 AI 的工具在收到"请直接修改"这种指令时会跳过思考过程直接输出,一旦方向偏了就得重来。让你确认一下,最多浪费 10 秒钟,但能把方向性问题拦住。
还有一个小坑:AI 经常会自作主张地换掉函数内部的数据结构。比如把一个数组改成 Map 以提高查找效率。这在理论上是正确的优化,但如果这个数据结构在其他地方也被读取了(哪怕只是 .length 属性),行为就会发生变化。别信任 AI 对数据结构的判断,除非你有完整的测试覆盖。
输出物:一个可以通过单元测试的单文件 diff。
Step 4:TDD守门,先写测试再让AI改
这一步是所有步骤中最关键的。没有这一步,后面的 6 步都是摆设。
很多人觉得 TDD 是在开发阶段用的,重构不需要。这是一个认知误区。恰恰相反,重构是唯一一个你可以随意改动代码而不怕引入 bug 的场景——前提是你有足够的自动化测试保护你。
@src/services/order-service.ts::calculateDiscount
在这个函数当前实现之前,先为一个已有的复杂函数编写完整的单元测试。
函数信息:
- 文件名:src/services/order-service.ts
- 函数名:calculateDiscount
- 当前长度:63 行
- 主要逻辑:根据用户等级、订单金额、促销规则计算最终折扣
测试要求:
1. 覆盖所有已知分支(用户等级 A/B/C/D、金额为0、金额为null、促销叠加)
2. 包含边界值:最大金额($99999)、最小负数情况、浮点精度场景
3. 每条测试必须有清晰的名字,描述输入和期望输出
4. 使用现有的测试框架(Jest/Mocha,根据项目中已有的约定)
5. 先输出测试代码,不要修改原函数
等待我确认测试通过后,再进行下一步。
写完测试之后,让 AI 修改原函数,同时运行测试。测试不通过的话,把错误信息喂回去让 AI 修复。反复这个过程直到全部通过。
这里有个实操技巧:如果你的项目测试覆盖率本身就低,不要试图在重构过程中把所有缺失的测试补齐。 那样你会花 80% 的时间写测试,20% 的时间改代码。先保证覆盖了你想改的那部分,其他部分的测试覆盖以后再说。
输出物:一套通过的全部测试用例,以及被测试保护的已修改函数。

Step 5:对抗性Code Review
测试全过了,别急着合 PR。先做一次对抗性的代码审查。
常规 Review 的问题是审查者对这段代码太熟了。看了三遍之后会产生一种"这没问题"的错觉。对抗性 Review 的做法是让 AI 扮演一个没见过这段代码的人,用最新鲜的上下文来挑刺。
@diff
假设你是一个第一次接触这个项目的新人工程师,正在 review 这份 PR。
你没有项目的历史背景知识,也没有读过相关的设计文档。
请从以下角度进行审查:
1. 可读性:作为一个新人,你能在 5 分钟内理解这段代码在做什么吗?哪里让你困惑?
2. 正确性:有没有看起来不太对劲的逻辑?即使暂时说不清错在哪里,也请标注出来
3. 一致性:代码风格是否与项目中已有的代码保持一致?逐一对比原有代码的风格差异
4. 安全性:有没有潜在的注入风险、信息泄露或未处理的异常?
5. 性能:有没有明显的性能问题,特别是循环中的数据库查询、不必要的序列化
对于每个问题,给出你在原始代码中对应的行号和具体的担忧。不要泛泛而谈。
对抗性 Review 的输出往往会让你的背脊发凉。不是因为 AI 多厉害,而是因为你在熟悉这段代码的过程中确实忽略了一些显而易见的东西。这就是所谓的"知识的诅咒"——你知道的太多,反而看不到别人会遇到的问题。
我一般会把这个 Review 结果当作一个 checklist,手动确认每一个标注点。有些 AI 的判断可能过度保守,但那也没关系,宁可多问一句也不要漏掉真正的问题。
输出物:一份 Review 记录,逐项关闭或接受每个发现的问题。
Step 6:灰度发布,feature flag 是你的保险绳
改完了,Review 过了,测试全绿。是不是可以直接 deploy 了?
不是。 至少不应该直接推到主分支的生产流量上。
feature flag 是最简单也最有效的灰度手段。不管你们用的是 LaunchDarkly、Unleash 还是自己写的一个 bool 开关,原理都是一样的:新代码走一条独立的执行路径,默认不生效,按需开启。
// 在你的入口处插入 feature flag 包裹
const USE_NEW_ORDER_FLOW = process.env.FEATURE_NEW_ORDER_FLOW === 'true';
if (USE_NEW_ORDER_FLOW) {
// 新的重构后的逻辑
} else {
// 旧的逻辑保持不变
}
// 部署命令
FEATURE_NEW_ORDER_FLOW=true npm run build && kubectl rollout restart deployment/order-service
// 监控命令
kubectl logs -f deployment/order-service -l app=order-service --tail=200 | grep ERROR
// 快速切回旧逻辑
FEATURE_NEW_ORDER_FLOW=false npm run build && kubectl rollout restart deployment/order-service
灰度的节奏我建议是这样的:先对内网开放(团队成员自己用),稳定半天无异常,再开放给 5% 的外部流量,观察 24 小时,逐步加到 50%,最后全量。每一步之间留出足够的时间让人反应。
别嫌麻烦。腾讯云计算 20 万行重构的经验,从 2000 行的巨型函数拆到 100 行以内,重复代码从 35% 降到 10% 以下,他们交付周期能压缩到 1.5 天的前提是每一层都有充分的验证。我们规模小不代表可以跳过验证。
输出物:可切换的 feature flag 配置 + 监控面板。
Step 7:回滚预案,提前想好最坏情况
最后一步也是很多人完全不做的一步:回滚预案。
Git 是你最好的朋友。在做任何大范围改动之前,先打一个 checkpoint。
# 重构开始前
git stash push -u -m "pre-refactoring-backup"
git tag -a "before-refactoring-order-service" -m "checkpoint before AI refactoring of order-service"
# 如果出问题,一键回退
git checkout "before-refactoring-order-service" -- src/services/order-service.ts
git checkout "before-refactoring-order-service" -- src/utils/order-utils.ts
# 或者更暴力一点,恢复整个分支
git reset --hard "before-refactoring-order-service"
配合 feature flag,你的回滚策略有两层:
- 快速止血:关闭 feature flag,旧代码立即生效,不需要重新部署
- 彻底回滚:如果 flag 关不住(比如配置错误导致走了新代码路径),直接从 git tag 恢复文件并重新部署
我习惯在重构开始前就在 README 或者 PR description 里贴好回滚命令。这样万一出了问题,任何人(不只是你自己)都能在 30 秒内把服务恢复到健康状态。
输出物:一个 git tag、一份回滚文档、一个能一键执行的回滚命令。
整合案例:OrderService 的 7 步旅程
拿一个实际的例子串起来看看效果。
我们团队最近要重构一个 OrderService,大约 1200 行,涉及订单创建、支付回调、退款三个核心链路。按照上述 7 步走下来:
第一步扫描出了 47 个问题,筛掉噪音后剩下 14 个 P0/P1,主要集中在 createOrder 和 calculateRefund 两个函数。第二步依赖梳理发现 order-service 引用了 6 个共享模块,其中 payment-cache 被另一个团队在用,标记为保护区。第三步开始逐个击破,先从 calculateRefund 下手,因为这个函数相对独立。第四步写了 23 条测试用例,覆盖了所有金额计算的边界场景。第五步对抗性 Review 揪出了一个问题:AI 把货币单位的字符串从大写改成了小写,虽然前端没报错,但下游结算系统是按大写匹配的。第六步上了 feature flag,先在 staging 跑了一天。第七步打了 pre-refactor-checkpoint tag。
整个过程花了两天半,比单纯让 AI 一口气改完慢了三倍。但是合进去之后没有任何线上事故,而且后续迭代效率明显提升。
对比我之前那篇帖子里踩的大坑,这次之所以顺利,靠的不是 AI 更强了,是流程更靠谱了。
FAQ
Q:单测覆盖率很低的情况下还要这么做吗?
A:更要这么做。覆盖率低意味着你需要保护的代码更多。先把你想改的那部分补上测试,不要追求一次到位。覆盖率可以从 20% 慢慢爬升到 60%,但前提是每一步都不能裸奔。
Q:小团队没有 feature flag 基础设施怎么办?
A:一个简单的 env var 就够用了。不需要上 LaunchDarkly 那种重型方案。REWRITE_ENABLED=true 加上一个 if-else,就能解决 80% 的灰度需求。腾讯云当年做 20 万行重构的时候,核心的环境切换也是靠配置项实现的。
Q:AI 写的测试靠得住吗?
A:不完全靠得住。你需要人工审核测试覆盖的逻辑完整性。AI 写测试的优势是快和格式规范,劣势是不一定能理解业务语义。把它当成一个高效的初稿生成器,不要当成最终的裁判。
Q:这套流程能不能用在小型重构上?
A:如果只改一个文件里面的一两个函数,可以简化。但至少保留 Step 4(单测守门)。其他步骤可以根据实际情况裁剪。核心原则是:能防止翻车的步骤不打折,不容易翻车的部分可以省略。
Q:多久做一次重构比较合理?
A:这个问题没有标准答案。我的经验是,只要静态分析发现了重复代码率超过 12% 或者有明确的 P0 问题(比如函数超过 100 行),就该安排。不要等所有指标都亮红灯了才动手,那时候技术债的利息已经很高了。
写在最后
AI 重构代码这件事,本质上不是技术问题,是工程管理问题。
工具会越来越强,这是事实。Anthropic 自己都说上下文窗口是瓶颈,各大厂商都在拼命加大。但不管上下文有多大,AI 对业务逻辑的理解永远无法替代人类工程师。它能做的是在你已经有清晰计划的前提下加速执行,而不是替你决定往哪个方向走。
如果你也在用 AI 做重构,试试这套 7 步 SOP。不用一开始就全盘照搬,可以先从 Step 4 起步——写完测试再让 AI 改,这一个动作就能拦住大部分翻车。关注我,下周会拆解一篇关于如何用 SonarQube + Claude Code 联动做持续重构的文章,内容更偏实操。 如果你身边也有同事正在被 AI 重构带来的 bug 折磨,把这篇文章转给他们,至少能少走两天弯路。

来源:博客园 · cnblogs.com