AI编程重构存量代码时如何控制改动风险

一个经常出现的场景:开发人员把存量模块交给AI做等价重构。AI生成的新版本阅读起来很顺,单元测试也通过,但Code Review时大家只看了业务主流程,没有察觉AI顺手把日志组件的初始化顺序调换了。上线后,一条依赖事件先后次序的下游任务被触发重复更新。问题不是出在某个业务大逻辑上,而是藏在AI认为可以顺手改一下的细节里。

对中大型项目来说,这类问题的危险性比小项目高得多。原因在于存量代码里有大量文档未说明、测试未覆盖、但线上已经依赖的行为。当一次AI重构生成几百行diff时,人工逐行比较无法可靠找出这些行为差异。真正可以长期依赖的,不是更认真Review,而是把重构拆成一段可以验证的流程。

先定义可观察边界,再让AI生成代码

许多重构失败的根源,不是AI把代码写错了,而是开发者没有告诉它哪些行为不能变。等价重构不是一个笼统概念,它至少要落到一系列可验证的约束上:

  • 函数签名与返回值结构不变;
  • 外部API调用次数、顺序、内容不变;
  • 错误抛出类型和冒泡路径不变;
  • 日志级别、字段顺序、数据格式不发生变化;
  • 并发控件的范围和线程切换语义不变。

在向AI描述任务时,把这些约束写进提示中,比如重构内部实现时不要改变对外调用顺序和异常类型。这一步的价值是双向的:既让AI输出时减少随机性,也给人提供了一个审查基准——如果diff中任何修改超出了申报范围,就是一个需要拦截的信号。

需要注意的是,即使AI在提示中读到这些约束,也不能保证它在所有分支中遵守。因此不要把约束当成给AI下命令,而当成生成后过滤diff的依据。

审查AI diff时,不要逐行比,而是做行为差分

AI生成的diff往往与人类重构的书写方式不同。它可能把一个函数内联到另一个函数,又顺手拆出三个私有工具方法。逐行比较容易陷入哪里变了,错过行为变化的证据。

一个可行的做法是,把diff从代码差异转换成行为差异,然后专门检查风险语义,而不是纠结它们的语法实现,重点关注几个方面:

  • 异常处理是否被合并。原代码对两类错误分别打印不同message并抛出不同异常,如果被AI合并成一个分支,调用方的重试和告警会失去区分度。
  • 资源释放是否改变触发时机。原本在finally中执行的操作,如果被移到某个提前return之后,就会释放资源或锁的时机带来偏移。
  • 循环与迭代顺序是否被看似无害的调整。对HashMap结果做排序,表面上是规范化,如果业务实际依赖旧版顺序,反而破坏原有假设。
  • 延迟与缓存逻辑是否消失。旧代码中为降低数据库压力做的限速或去重逻辑,容易被当成无用代码删除。
  • 空值处理方式是否变化。显式null检查替换为Optional后,外层调用方原本依赖的NPE状态回滚行为可能会消失。

这些场景可以整理成PR模板中的固定检查项。关键不是找AI别改什么,而是让评审者确认,新旧代码在同一输入下是否存在无法解释的差异。如果有,就不应该合并;如果没有,才可以继续往下走。

没有测试的老代码,先加回归基线

让AI安全重构的前提,是改动后的代码能被快速验证。如果现有测试覆盖率不足,就无法把回归责任全部压在测试上。一个常见做法是在重构前补充特征化测试:不按理想业务预期写断言,而是用当前代码的真实输出来锁定现有行为。

具体步骤是:

  1. 找出要重构的模块可被外部触发的入口;
  2. 运行当前代码,收集这些入口的代表性输出;
  3. 将这些输出落成快照或断言,作为回归基线;
  4. 运行AI重构后的代码,用同一批输入做对比;
  5. 对每一条不匹配的差异,人工判断是回归还是预期修复。

这个方法不要求团队先理解所有业务规则,却能把行为发生变化的具体位置快速暴露出来。而且它应该在AI重构前完成。等AI生成完diff再补测试,通常很难判断差异来自重构,还是来自测试本身不稳定。

用可独立回滚的方式组织提交

假如AI一次生成了20个文件的改动。即使每个文件编译通过,这样的改动也很难通过Review。因为一旦出问题,没人能判断是哪一层引起的。

更合适的提交单元不是一个AI会话产出的全部结果,而是一条依赖链上的一个步骤。比如先提交底层工具函数,再逐个提交调用方,最后删除旧实现。每个步骤提交后都要保证项目可编译、可运行。如果下一步出现问题,可以直接回滚这一步,不会连带上一步已经验证的内容。

还有一个容易被忽视的点:不允许AI在重构之外顺手修改不相关的内容。import排序、for循环改成stream、private方法调整可见性,这些都会扩大diff范围。Review时如果发现和重构意图无关的修改,应该要求剔除后重新生成,而不是因为它们看着无害就收下。

当AI把代码改得太干净时,反而要更谨慎

存量系统代码保持低抽象度,不一定是因为开发者水平不足,更多时候是因为边界条件被打成了补丁。重复的if分支,可能对应不同环境下的特殊值;看上去冗余的参数,可能是在兼容旧调用。

如果AI重构之后的代码比原版短很多,分支明显变少,就要追问被删除条件都到哪里去了。一个可行的判断标准是:每一个被删除的显式分支,都要能说出它对应的业务规则或环境约束;说不出来,就默认它是防御性逻辑,先保留。

反过来,AI主动引入抽象层也要谨慎。抽象层一旦被多个调用点使用,未来定位问题时的影响范围比原来直接写在业务函数里更大。除非新抽象能显著降低重复,否则在存量代码中不建议让AI顺手新增。

安全重构不能只依赖模型的自校正能力

从工程角度看,AI编程工具改变的是代码生成的效率,没有改变验证责任。任何AI建议的修改都应该先被视为候选代码,再进入编译、静态检查、自动化测试、代码评审和回归验证这几道基础门槛。

如果项目连基本的构建保护、单测框架和主干合并限制都没有建立,就不适合让AI大规模重构存量模块。这样的重构一旦出错,团队无法区分是当前改动带来的回归,还是历史问题被翻了出来。维护成本会被进一步抬高。

先把工程基础打牢,把一次AI重构压缩成可理解、可验证的小步动作,再谈效率,是控制存量代码风险更现实的一条路。