让 AI 重构代码是眼下很流行的用法:把一段又长又乱的老代码贴给 DeepSeek,让它「重构得清晰一些」,几秒钟回来一份命名整齐、结构漂亮的版本。看着确实舒服,可合进项目一跑,问题来了:某个接口突然返回空值,某个定时任务悄悄不跑了。重构翻车的根源大多相同——代码里那些没写出来的隐含约定,在重写过程中丢了。
重构翻车的典型现场
翻车现场有几个高频剧本。其一是改名改炸:AI 把某个字段名改得更规范,风格确实好了,但前端在按旧字段名解析数据,某个报表按字符串拼接取列名,全跟着断。其二是改签名改行为:函数参数被重排、参数多了个「更合理」的默认值,调用方没跟着改,默认路径悄悄变了。其三是合并与提取:三个相似循环被合并成一个通用函数,可其中一处恰好依赖循环的执行顺序,或依赖某次循环里的副作用,合并之后顺序没了。共同点很清楚:每一处改动单看都更优雅,合起来把项目赖以运转的默契拆掉了。
隐含约定藏在哪里
隐含约定指代码里没有明说、但整个系统都在默默遵守的规矩,常见的藏在几处。命名即协议:数据库字段名、接口返回的键名、消息队列的消息格式,很多下游在按字面字符串引用它们,改名就等于改协议。顺序即逻辑:先刷新缓存再读库、先校验再落库,步骤之间的先后是业务要求,不写进注释,外人看是多余的。环境即输入:代码读环境变量、读配置文件、依赖某个服务已经启动,这些前提在代码里看不到。版本即行为:某个依赖库在特定版本下的函数行为有已知差异,换写法或升级都会触发。这些约定大多长在项目的角落里,不在你贴出去的那段代码之内。
| 约定的类型 | 具体长什么样 | 丢了会出什么事 |
|---|---|---|
| 命名即协议 | 数据库字段名、接口键名、消息队列格式被下游按字面引用 | 改名等于改协议,前端解析与报表取列一起断 |
| 顺序即逻辑 | 先刷缓存再读库、先校验再落库 | 几个循环合并后顺序没了,业务要求落空 |
| 环境即输入 | 读环境变量、读配置文件、依赖某个服务已启动 | 换个环境跑不起来,代码里看不出来 |
| 版本即行为 | 某个依赖库在特定版本下的函数行为有差异 | 换写法或升级就会触发已知差异 |
AI 为什么看不见它们
AI 重构的视野是你给它的上下文:贴一段它看一段,就算用上能读仓库的工具,读到的也只是文件内容的一部分。模型学到的是公开代码里的通用惯例,它会把你项目里的写法往「标准写法」上靠,而项目里那些非标准的、历史原因形成的特殊约定,恰恰是它要「纠正」的对象。换句话说,两边追求的目标不同:AI 追求通用意义上的清晰,项目需要的是局部意义上的守约。这不是模型能力不行,是任务性质决定了它拿不到全部信息。所以在贴代码之前先盘点一层:这段代码和系统里哪些地方有暗中往来。顺带一提,贴码本身也有边界,公司代码怎么分级、贴之前怎么脱敏,代码保密的三个层级一篇讲过完整流程。
重构前后的回归测试
让重构可控的流程并不复杂,四步就够。第一,先补测试再动手:核心路径没有测试的,先让 AI 起草测试用例,人工补上边界条件,跑绿了再开始重构。第二,小步走:一次只重构一个函数或一个模块,改完立刻跑全套件,看退出码而不是扫一眼输出里的 OK 字样。第三,对照行为:重构前把关键函数的输入输出存下来,重构后逐个比对,序列化结果做 diff,字符串形式的输出最容易暴露字段名被动过。第四,敏感改动留人审:涉及协议、存储、鉴权的重构,逐行看懂再合,审校的落点见代码补全的正确打开方式一篇。还要记住一条:重构产生的提交一样由提交人负责,出了问题说「是 AI 改的」没有用。工具上,DeepSeek 和通义千问对中文注释项目的理解都不错,但工具越顺手,越要守住测过再合这条线。
延伸阅读:回归测试集怎么建。
重构前自查四问
- 查协议:字段名、键名、消息格式有没有下游在引用
- 查顺序:步骤先后是业务要求还是随手写的
- 查环境:代码依赖了哪些看不见的前提
- 查测试:核心路径有测试兜底吗,没有先补
再多说几句 AI 重构
- 让 AI 自己检查重构有没有破坏约定,可行吗?:可以让它列出「这段代码可能被外部依赖的点」,清单有参考价值,但模型看不见仓库全貌,清单不全,最终以回归测试的结果为准。
- 老代码乱但能跑,值得让 AI 重构吗?:看动机。为了加新功能顺手理清局部,值得;为了看着舒服大动核心逻辑,收益配不上风险,跑得稳的老代码本身也是资产。
- 重构后线上出了问题,怎么定位是不是重构引起的?:先回滚到重构前的版本验证,问题消失基本可以锁定;再对着改动清单逐块排查,重点看命名、顺序、默认值这三类改动。


