banner
NEWS LETTER

AI 辅助重构:把大改动拆成可验证的小步骤

Scroll down

重构最怕的不是代码改得多,而是改完以后说不清楚行为有没有变化。AI 工具能很快移动代码、抽函数、改命名、补类型,但如果缺少边界和验证,大规模重构很容易从“改善结构”变成“引入不确定性”。

更稳妥的做法,是把 AI 当成一个执行力很强的工程助手,让它在明确目标、有限范围和可重复验证的约束下工作。重构不应该一次性追求“变得优雅”,而应该被拆成一组可以审查、可以回滚、可以证明行为不变的小步骤。

一、先判断是不是应该重构

不是所有看起来不舒服的代码都值得马上重构。重构应该服务于具体目标,而不是单纯追求形式整洁。

比较值得启动重构的场景包括:

  • 同一段业务规则在多个位置重复出现。
  • 一个函数同时负责校验、计算、存储和展示。
  • 新需求每次都要改很多无关文件。
  • 异常路径散落在调用链里,问题难以定位。
  • 测试很难编写,因为逻辑和外部依赖耦合太紧。
  • 命名和模块边界已经无法表达真实业务含义。

如果只是局部命名不喜欢,或者短期不会继续演进,重构的收益可能不够高。AI 让修改成本下降了,但 review、测试和回归风险并不会自动消失。

二、给 AI 一个明确的重构目标

“帮我重构这段代码”太宽泛,AI 很容易顺手做过头。更好的任务描述应该包含目标、范围和禁止事项。

例如:

1
2
3
4
请只重构订单金额计算逻辑。
目标是把折扣、运费、积分抵扣拆成独立函数,保持外部接口不变。
不要修改数据库结构、接口字段和 UI 文案。
完成后补充或调整现有单元测试。

这类说明会让 AI 知道什么可以动,什么不能动。尤其是“保持外部接口不变”很重要,它能把任务限制在内部结构调整,而不是演变成一次行为变更。

如果重构涉及多个模块,可以进一步要求分阶段:

1
2
3
4
第一步只提取纯函数,不改变调用方。
第二步补测试覆盖边界输入。
第三步再替换旧实现。
每一步完成后都运行测试。

AI 很适合执行这种机械但需要细心的步骤。人负责判断方向,AI 负责把小步骤做完整。

三、先建立行为保护网

重构的核心原则是:结构可以变,行为不能无意变化。要做到这一点,最好在动代码之前先有保护网。

保护网不一定一开始就很完整,但至少要覆盖最关键的业务假设:

  • 正常输入得到稳定结果。
  • 空值、非法值和边界值不会导致崩溃。
  • 失败路径返回可预期错误。
  • 重复调用不会产生额外副作用。
  • 对外接口的响应结构不变化。

对于纯逻辑代码,优先补单元测试。对于 Android 项目,可以先覆盖 ViewModel、Repository、格式化器、状态转换等不依赖 UI 的部分。对于后端服务,可以覆盖参数解析、权限判断、幂等逻辑和错误码。

如果当前代码很难测试,也不要一上来大拆。可以先加几条高价值的黑盒测试,固定住外部行为,然后再逐步把内部逻辑抽出来。

四、每次只改一种事情

重构最容易失控的地方,是在同一个提交里混合太多类型的变化。

常见的混合包括:

  • 改命名的同时改业务逻辑。
  • 移动文件的同时升级依赖。
  • 抽象公共模块的同时改接口返回值。
  • 格式化全文件的同时修 bug。
  • 删除旧代码的同时替换调用链。

这些改动放在一起,review 会变得很痛苦。审查者很难判断某一行变化到底是行为修复、结构调整,还是格式化带来的噪音。

更稳的拆法是:

  1. 先补测试,证明当前行为。
  2. 再做纯移动、纯命名、纯提取函数这类机械改动。
  3. 然后替换调用方。
  4. 最后删除旧实现。

每一步都应该能独立构建或通过测试。如果其中一步出问题,也更容易回滚。

五、让 AI 输出可审查的 diff

AI 一次性改太多文件时,最重要的不是它“看起来完成了”,而是 diff 是否容易审查。

可以在任务里明确要求:

1
2
3
4
请控制单次改动范围。
如果需要修改超过 5 个文件,先说明拆分方案。
不要做无关格式化。
不要重排 import 以外的无关代码。

完成后还可以让 AI 自查一次:

1
2
3
请检查这次 diff 是否存在无关改动。
请按文件说明每处修改的目的。
请指出是否有外部行为变化。

这一步的价值在于把“代码变化”翻译成“工程意图”。如果 AI 自己都无法清楚说明某个文件为什么被改动,通常说明这部分变化需要重新拆分。

六、处理遗留代码要先隔离副作用

遗留代码难重构,通常不是因为语法复杂,而是因为副作用太多。一个函数里可能同时读配置、访问数据库、发网络请求、写日志、更新 UI 状态。

面对这种代码,第一步不是直接抽象接口,而是先识别副作用边界:

  • 哪些输入来自外部环境。
  • 哪些输出会改变持久化数据。
  • 哪些调用可能失败或超时。
  • 哪些状态依赖调用顺序。
  • 哪些逻辑可以变成纯函数。

可以先让 AI 只做分析,不做修改:

1
2
3
请阅读这个模块,列出纯计算逻辑和外部副作用。
先不要修改代码。
请指出哪些部分适合先提取为可测试函数。

等边界清楚后,再让它做最小提取。比如把金额计算、状态判断、文本格式化先抽出来,数据库和网络访问仍然留在原位置。这样风险会小很多。

七、命名重构要保持业务语义

AI 很擅长批量改名,但命名不是单纯把变量变长。好的命名应该表达业务语义,而不是只描述技术形状。

例如:

1
2
3
4
data -> orderSummary
result -> paymentDecision
flag -> shouldRetry
list -> pendingInvoices

命名重构时要注意两点:

  • 不要把领域概念改成模糊的技术词。
  • 不要为了统一风格破坏已有公共 API。

如果命名会影响序列化字段、数据库列、路由参数、前端埋点或第三方协议,就不能当作普通变量重命名处理。AI 批量替换前,必须先确认这些边界。

八、验证不只跑一次

重构后的验证应该分层,而不是只在最后跑一次构建。

比较实用的节奏是:

1
2
3
4
小步骤完成后:运行相关单元测试。
调用链替换后:运行模块测试。
公共接口变化后:运行集成测试或构建。
最终完成后:运行项目约定的完整验证命令。

对于博客、前端或脚本项目,构建命令就是最低要求。对于应用项目,还要结合单元测试、静态检查、打包和关键页面手动验证。

如果某些验证因为环境缺失无法执行,结果说明里要写清楚:

1
2
已运行 npm run build,通过。
未运行端到端测试,因为本地没有测试服务依赖。

这比一句“应该没问题”可靠得多。

九、保留回滚路径

好的重构应该能被回滚。不是说一定会失败,而是要避免把风险绑死在一个巨大提交里。

可以用几种方式保留回滚空间:

  • 分多次提交,每次只做一种变化。
  • 保持旧接口一段时间,先让新实现并行接入。
  • 对高风险路径加开关或灰度条件。
  • 删除旧实现前确认调用方已经全部迁移。
  • 在变更说明里写清楚验证范围和风险点。

AI 可以帮助生成迁移清单,但最终仍然要由维护者判断是否可以删除旧逻辑。尤其是被反射、配置、脚本或第三方调用的代码,不能只靠静态搜索下结论。

十、一个可复用的重构提示词

日常可以把下面这段作为起点:

1
2
3
4
5
6
7
8
9
10
请以小步重构方式处理这个模块。
目标:改善结构和可测试性,保持外部行为不变。
要求:
1. 先阅读相关文件,说明当前边界和风险。
2. 只修改完成目标所需的文件。
3. 不做无关格式化、依赖升级或命名风格调整。
4. 优先提取纯函数和明确副作用边界。
5. 补充或调整能证明行为不变的测试。
6. 完成后运行项目约定验证命令。
7. 最后说明改了什么、验证结果和残留风险。

这段提示词的重点不是让 AI 变得保守,而是让它把重构当成工程交付,而不是代码美化。

十一、总结

AI 让重构的执行速度变快,但重构的本质没有变:先理解行为,再改善结构,最后用验证证明没有引入意外变化。

真正可靠的 AI 辅助重构,应该具备三个特征:

  • 目标明确,知道为什么改。
  • 步骤足够小,每一步都能审查。
  • 验证可重复,结果能被复盘。

当重构被拆成可验证的小步骤后,AI 的优势会非常明显。它可以承担大量重复、细碎、容易疲劳的修改工作,而开发者把注意力放在边界、风险和设计判断上。这样的协作方式,才更适合长期维护的真实项目。

其他文章
目录导航 置顶
  1. 1. 一、先判断是不是应该重构
  2. 2. 二、给 AI 一个明确的重构目标
  3. 3. 三、先建立行为保护网
  4. 4. 四、每次只改一种事情
  5. 5. 五、让 AI 输出可审查的 diff
  6. 6. 六、处理遗留代码要先隔离副作用
  7. 7. 七、命名重构要保持业务语义
  8. 8. 八、验证不只跑一次
  9. 9. 九、保留回滚路径
  10. 10. 十、一个可复用的重构提示词
  11. 11. 十一、总结
请输入关键词进行搜索