AI 写的代码怎么维护?2026 反屎山实战指南
用 AI 把项目从零做到能跑,现在已经不难了。真正的难题出现在两三个月之后:需求一改就牵一发动全身,同一个功能在三个地方各写了一遍,报错栈指向一段你完全没印象的代码——AI 写的代码怎么维护,才是这一波 vibe coding 留给所有人的真问题。这篇文章不讲「别用 AI 写代码」这种废话,而是给一套可执行的流程:先判断代码是不是真的在腐化,然后止血、建地图、分阶段重构,最后把治理动作固化下来,让 AI 下次就写出能维护的代码。
先分清:是「看着乱」还是「真的在腐化」
AI 生成的代码风格常常和你的习惯不一样,但风格不统一本身不是技术债。真正需要动手的是下面这些信号,出现三条以上就说明代码库已经在腐化,而不只是不好看。
- 同一个逻辑有多份实现。AI 在不同会话里没有全局记忆,很容易把「格式化日期」「校验手机号」这类工具函数重写好几遍,散落在各个文件里。
- 改一处崩三处。模块之间没有清晰边界,AI 为了让当前需求跑通,直接把上层逻辑塞进了底层函数,形成隐蔽的反向依赖。
- 风格像「百家衣」。同一个项目里既有回调又有 async/await,既有类又有纯函数——AI 的输出风格会随上下文窗口内容剧烈波动。
- 没有人能解释某段代码为什么存在。当初是 AI 为了绕过一个报错加上的,注释写着「确保兼容性」,但没人知道兼容什么。
- 不敢删任何东西。缺少测试覆盖,任何删除都像拆盲盒,于是死代码只增不减。
先做判断,再做动作
如果只中了一两条,且项目还在快速迭代期,最划算的做法是先记下来、继续推进业务。技术债和金融负债一样,适度举债是合理的——真正致命的是从不记账、也从不还。
第一步:止血,而不是马上重构
看到一堆烂代码,第一反应往往是「让 AI 重构一遍」。这几乎是最坏的选择——在没有测试保护的情况下让 AI 大范围改动,你会得到一份「看起来更整洁、但功能悄悄坏了」的代码,而且完全不知道是哪一步坏的。正确的顺序是先止血。
- 1冻结范围。明确这次只处理哪几个文件或哪一个模块,其余部分一律不动。给 AI 的指令里写清楚边界,否则它会顺手「优化」你没让它碰的地方。
- 2补一张测试网。不用追求覆盖率,先针对核心链路补几个端到端测试——只要能回答「改完之后这个功能还对不对」就够了。这类测试的作用是锁住当前行为,哪怕当前行为本身并不优雅。
- 3锁住依赖版本。确认 lockfile 已提交,避免重构过程中依赖悄悄升级,让你分不清故障来自你的改动还是来自新版本。
- 4开一个独立分支,并保证能一键回滚。每个重构阶段单独提交,出问题直接回退到上一个可用点。
# 先建立安全网:让 AI 针对现有行为补测试,而不是先改代码
# 注意措辞——是「锁住当前行为」,不是「写出正确的测试」
git checkout -b refactor/user-module
# 在 AI 编程工具里给出这样的指令:
# 「阅读 src/services/user.ts,为它当前的实际行为补充端到端测试。
# 不要修改任何业务代码。如果发现现有行为看起来像 bug,
# 也照现状写进测试,并在测试旁边用注释标注疑似问题。」
npm test # 确认全绿,这是你的基线别让 AI 一边补测试一边改代码
这是最常见的翻车方式:AI 发现代码有问题,顺手改了,再写一个「通过」的测试。结果测试和代码一起变了,你失去了基线,也失去了判断依据。补测试和改代码必须是两个独立的提交。
第二步:给代码库建一张地图
AI 之所以越改越乱,本质原因是它每次只看得见上下文窗口里的那一小块。你要做的是把「整体结构」显式地写下来,让它每次动手前都能读到。这份地图不需要很长,但必须准确。
- 模块清单:每个目录 / 模块负责什么,一句话说清。
- 依赖方向:谁可以调用谁,明确写出禁止的反向依赖(比如「数据层不得引用业务层」)。
- 关键决策记录:为什么选了这个方案、当初排除了什么,避免 AI 好心「优化」掉一个有意为之的设计。
- 危险区域:哪些文件改动风险高、必须人工评审。
让 AI 帮你生成初稿是可行的,但一定要人工过一遍——它会把「现状」误当成「设计意图」写进去。生成之后把这份地图放进项目根目录的 AGENTS.md 或 CLAUDE.md,主流 AI 编程工具都会自动读取。
# 让 AI 先只读不写地摸清结构(Claude Code 里连按两次 Shift+Tab 进入计划模式)
# 指令示例:
# 「通读整个仓库,输出一份架构说明:
# 1) 每个顶层目录的职责,一句话
# 2) 模块之间的实际依赖关系,标出你认为不合理的反向依赖
# 3) 你发现的重复实现(同一逻辑写了多遍的地方),列出文件和行号
# 只输出报告,不要修改任何文件。」第 3 条尤其有价值——扫重复实现是 AI 相对人类的强项,它能在几分钟内跑遍整个仓库,找出你早就忘了的那三份日期格式化函数。这份清单直接就是你后面重构的工作项。
第三步:渐进式重构的四个阶段
有了测试网和地图,才轮到真正动手。核心原则只有一条:每次改动都要小到「出问题能一眼看出是哪一步」。以拆解一个几百行的巨型函数为例,合理的节奏是分四个阶段走。
| 阶段 | 做什么 | 验证方式 | 为什么不能跳过 |
|---|---|---|---|
| 1. 提取 | 把明显独立的片段抽成辅助函数,不改变任何逻辑 | 测试全绿 + diff 里没有逻辑变化 | 先降低复杂度,后面的改动才看得清 |
| 2. 扫引用 | 动任何函数之前,先让 AI 搜出所有调用点 | 人工确认引用清单完整 | AI 常常只改了定义,漏掉两三个调用方 |
| 3. 重组 | 按新的模块边界重新组织,一次只搬一个文件 | 每搬一个跑一次测试 | 一次搬十个文件,坏了也不知道是哪个 |
| 4. 并行切换 | 新旧实现同时保留,用开关切流,观察一段时间再删旧的 | 线上无异常后再清理 | 给自己留一条不需要回滚代码的退路 |
第 2 步经常被忽略,但它是 AI 重构最容易出错的地方。AI 修改一个函数签名时,往往只处理了当前上下文里可见的调用方,仓库另一头那两处调用就被静默漏掉了——如果那部分没有测试覆盖,问题会一直潜伏到上线。所以每次改签名前,先单独让它跑一次全局引用扫描,并且人工核对。
善用只读的计划模式
Claude Code 里连按两次 Shift+Tab 可进入计划模式,AI 只分析不改文件,先给出完整方案等你批准。多花五分钟审计划,能省掉后面半小时的回滚。其他主流工具也有类似的「先规划后执行」模式,重构类任务默认都该走这一步。
第四步:把共识固化成 AI 能执行的规则
重构完只是回到起点,如果不改变生产方式,三个月后会再烂一次。美团技术团队在一次 31 万行代码的 AI 重构实践中给出的思路很有代表性:先「人人对齐」形成团队共识,再把共识固化为「人机对齐」的可执行约束。当代码库里绝大部分代码由 AI 生成时,决定系统走向的已经不是谁写得更快,而是你约束 AI 的能力——没有统一规范,AI 只会成倍放大混乱。
- 把规范写进 AGENTS.md / CLAUDE.md,而不是写进 Wiki。AI 读不到的规范等于不存在。
- 规范要具体到可判断。「代码要清晰」没用;「新增接口必须先在 types 里定义类型,禁止 any」才是 AI 能执行的。
- 把重复检查做成流程。在提 PR 之前跑一轮 AI 自检(是否引入重复实现、是否违反依赖方向),比在评审时靠人眼发现有效得多。
- 技术债随迭代消化。不要攒成一个「重构专项」去申请排期——拆成每个业务需求的顺带动作,一次还一点,才可能真的还得完。
- 定期重新生成架构地图。代码变了地图没变,AI 就会照着过期的地图继续跑偏。
# AGENTS.md 片段示例:把约束写成 AI 能照做的形式
## 依赖方向(硬性)
- `src/lib/` 不得引用 `src/app/` 或 `src/components/`
- 数据访问只能通过 `src/lib/db/`,禁止在组件里直接查库
## 改动前必做
- 修改任何导出函数的签名前,先全局搜索调用点并列出清单
- 新增工具函数前,先搜索 `src/lib/utils` 是否已有同类实现
## 禁止事项
- 禁止为了让测试通过而修改测试断言
- 禁止在一次提交里同时补测试和改业务代码什么时候该推倒重来
渐进式重构是默认答案,但不是万能的。下面几种情况,重写反而更省:
- 项目规模还小(比如几千行以内),且当初就是一次性 vibe 出来的原型。这种情况下带着已有的需求认知重写一遍,通常比理清旧代码更快。
- 核心数据模型就是错的。表结构、状态机这类地基性设计有问题时,上层怎么重构都是徒劳。
- 没有任何测试,且业务逻辑已经没人说得清。此时连「锁住当前行为」这个前提都不成立。
- 技术栈本身要换。既然要换框架,就没必要先把旧代码整理干净。
反过来,只要项目已经有真实用户、有线上数据、有一堆边界情况被踩出来了,就别重写——那些散落在各处的「看起来多余的判断」,很多正是当初解决线上问题留下的。重写会让你把踩过的坑再踩一遍。
从「能跑起来」到「能长期维护」,中间隔着的不是更强的模型,而是一整套工程习惯:测试怎么补、边界怎么划、规范怎么让 AI 读到、重构怎么分阶段。这些东西 AI 不会主动教你,但它们决定了你的项目是能一直迭代下去,还是三个月后推倒重来。想系统补上这套工程能力,欢迎来 IMAI 看看我们的 AI 编程实战课程。
想系统学会用 AI 编程,从入门到做出真实产品?
查看系统课程