AI 重构把还有用的逻辑删了:3 个原因 + 修复路径

AI 把 50 行重构成 28 行看着漂亮,两天后客服群冒出登录跳错页——它把某个客户场景的兜底分支当死代码删了。本文教你用 bisect 加 prod 日志精确定位并加回。

让 Claude Code 或 Cursor”重构一下这个 handler 让它更干净”,diff 看起来漂亮——50 行变 28 行——你 merge 了。两天后客服群里出现”老用户登录跳错页”或”safari 上传文件失败”——AI 把它判定为”看起来没用”的某个分支删掉了,但那个分支正是某个客户场景的兜底逻辑。

本文给你一条”用 bisect 精确定位删了什么、对照 prod 日志找出真正在跑的分支、把被删的逻辑加回来并补上测试防回归”的修复路径。

常见原因

按命中率排序。

1. AI 认为”没测试”就是”没用”

最常见的失败模式。Agent 看到一段 if (user.legacyRole === "admin") 没有覆盖测试,就判定为 dead code 删掉。但 legacy admin 在 prod 里有 2% 流量,删了立刻有人 401。

- if (user.legacyRole === "admin" || user.role === "admin") {
-   return grantAdminAccess(user);
- }
+ if (user.role === "admin") {
+   return grantAdminAccess(user);
+ }

如何判断:被删的分支条件涉及 legacyoldv1fallbackdeprecated 这些词,或者引用了不再活跃但仍存在的字段。

2. 边界 case 没写在 prompt 里

你说”简化这个 parser”,没说”必须保留对空字符串、Unicode 零宽字符、CRLF 的处理”。Agent 拿掉所有它觉得”防御性过头”的判空、字符规范化代码。

- if (!input || input.trim() === "") return defaultValue;
- input = input.normalize("NFC").replace(/​/g, "");
  return parse(input);

如何判断:Prod 报 TypeError: Cannot read properties of undefined 或乱码错误,且错误堆栈指向被重构过的函数。

3. 重构范围太模糊

“清理一下这个文件”或”让代码更现代”这种 prompt 让 agent 自由发挥。它顺手把”看起来是临时 hack”的 retry / timeout / circuit breaker 一起删了——因为没注释解释为什么需要。

如何判断:被删代码周围没有解释性注释,且 git blame 显示该代码是某次 incident postmortem 后加的。

4. AI 把 feature flag 分支当成已废弃

- if (isFeatureEnabled("new-checkout", user)) {
-   return newCheckoutFlow(user);
- }
- return oldCheckoutFlow(user);
+ return newCheckoutFlow(user);

Agent 假设 flag 已经 100% rollout 就把另一边删了;但其实 flag 还在 50%,删完一半用户立刻进了未充分测试的新路径。

如何判断:被删代码包含 isFeatureEnabled / getFlag / LaunchDarkly 类的开关判断。

5. AI 简化 error handling,把”沉默吞掉”也删了

某些 try { ... } catch (e) { /* intentionally swallowed */ } 是真的有意吞掉(比如 analytics 上报失败不能阻塞 checkout)。AI 看到空 catch 就改成 throw 或 logger.error,结果支付流程被 analytics 报错挂掉。

如何判断:日志里突然冒出大量之前没有的 ERROR 级别条目,且业务功能反而开始挂。

6. AI 删了”看起来重复”的方法但其实签名不同

Agent 把 getUserById(id: string)getUserById(id: number) 重载合并成一个,删掉了字符串 ID 的兜底解析。但旧 URL 还带字符串 ID,直接 404。

如何判断:TypeScript 报 overload 错或 runtime 报 id is undefined

最短修复路径

Step 1:跑完整测试套件,先看挂了哪些

npm test -- --reporter=verbose 2>&1 | tee /tmp/test.log

但不要只信测试——如果是”没测试覆盖的分支被删”,测试反而全绿。同时跑:

# E2E / 集成测试
npm run test:e2e

# 手工 QA 关键场景(按业务列清单跑一遍)

Step 2:从 prod 日志反查真正在跑哪些分支

打开过去 7 天的 prod 日志,搜被改文件相关的入口:

# 例如:找最近 7 天调用 checkout handler 的 user agent / role 分布
grep "POST /api/checkout" /var/log/app.log | awk '{print $7}' | sort | uniq -c

输出会告诉你”实际在跑的代码路径分布”——比如发现 8% 流量 user-agent 是 IE/Safari,但 AI 重构后把那个分支删了。

Step 3:用 git bisect 定位回归 commit

如果回归是在多个 commit 之内引入的,bisect 是最快办法:

git bisect start
git bisect bad HEAD                    # 当前坏
git bisect good v1.2.3                 # 上个已知好的 tag
git bisect run npm test -- failing-spec
# 或者手工:每轮 git bisect good/bad

bisect 自动二分,几轮就定位到引入问题的那个 AI commit。

Step 4:对比 diff,把被删的逻辑加回来

git show <bad-commit> -- src/checkout/handler.ts

逐段看被删的代码,按下面表格决定:

被删代码处理
真的是 dead code留着 deleted,写 commit 解释为什么确定无用
还在用但写法过时现代化重写,但保留行为
处理某个边界 case直接 revert 该 hunk
Feature flag 分支查 flag 状态;没 100% rollout 就 revert

revert 单个 hunk:

git checkout <good-commit> -- src/checkout/handler.ts
# 或精确到 hunk
git checkout -p <good-commit> -- src/checkout/handler.ts

Step 5:补上测试再 commit

被 AI 删了一次的逻辑,下次还会被删。立刻补上测试:

// 例:legacy admin 必须能登录
test("legacy admin role still grants access", () => {
  const user = { id: 1, legacyRole: "admin" };
  expect(authorize(user)).toBe(true);
});

测试名要说清”为什么”——不只是 “test legacy admin”,而是 “legacy admin role still grants access (regression: removed in refactor 2026-05-22)“。

预防建议

  • 只让 AI 重构有测试覆盖的代码——没测试就先补测试再重构
  • Prompt 显式列出”必须保留的行为”:"保留对空字符串 / IE11 / legacy admin 的处理"
  • 重要的边界处理加 // IMPORTANT: handles X case from incident #1234,agent 看到注释会更谨慎
  • 在 CLAUDE.md / AGENTS.md 写:“禁止删除有注释、引用 feature flag、或包含 ‘legacy’ / ‘fallback’ 关键字的代码分支,除非显式被要求”
  • 大重构强制走 PR,diff 里超过 30 行删除就 require 至少一个人 review
  • 接入 AI 提交前审查工作流,让 agent 先自己 diff 解释每一段删除的理由

相关阅读

标签: #AI 编程 #排查 #排查