让 AI 重构老代码,我宁可它一次只动一小步

  created  by  鱼鱼 {{tag}}
创建于 2026年10月11日 09:39:50 最后修改于 2026年10月11日 09:39:50

我手上有个老项目,里面有一个结算相关的 Service 类,六百多行。最长的一个方法两百多行,if-else 套了五六层,中间还夹着几段被注释掉、但没人敢删的代码。每次需求要动它,大家都绕着走,能加个分支就加个分支。

有一天我终于下决心收拾它。那时候我已经用 AI 写了不少代码,心想重构这种“体力活”,交给它正合适。

结果第一次尝试,差点让我对“用 AI 重构”这件事彻底失去信心。后来换了一种做法,才算顺利做完。这篇就讲讲这两次的区别。

第一次:一句“帮我重构得清晰一点”

我把整个类贴给 AI,说:“这个类太乱了,帮我重构得清晰一点,保持功能不变。”

它给我的结果,单看确实漂亮:长方法拆成了十几个小方法,几种结算方式抽成了策略类,变量名全换成了更有意义的名字。它还很贴心地附了一段说明,说“顺便修复了几处潜在问题”,比如某个地方金额舍入的方式“不一致”,某个空值判断“遗漏了”。

然后我看了一眼 diff:几百行改动,几乎每一行都变了。

问题来了:我怎么知道它真的“功能不变”?

这个类几乎没有单元测试。它说保持功能不变,但我没法验证。那几处“顺便修复”更让我心里发毛——我去翻了一下提交记录和需求文档,其中一处舍入方式不一致,是当年为了对齐某个合作方的对账规则故意这么写的。它眼里的 bug,是业务上的约定。

那份 diff 我最后一行都没合。不是 AI 写得不好,而是验证它的成本全落在我身上,而我根本验证不过来。

问题出在哪

事后复盘,我觉得问题有三个,都不是 AI 的“智商”问题,而是我给它的任务本身就有毛病。

一是重构和行为变更混在了一起。 重构的定义就是不改变外部行为,只改内部结构。可它在重构的同时“顺手修 bug”,等于把两种性质完全不同的改动搅在一个 diff 里。一旦出问题,我根本分不清是结构改坏了,还是“修复”改坏了。

二是步子太大。 一次改几百行,哪怕每一行都对,我也没法在合理的时间里确认它们都对。人脑做代码审查是有容量上限的。

三是没有安全网。 没有测试,就没有“改完之后行为没变”的证据,只能靠肉眼和信心。

想清楚这三点,第二次的做法就自然出来了。

第二次:先锁住行为,再一步一步走

第一步:写特征测试

在动任何代码之前,我先让 AI 帮我写“特征测试”。这个概念我是从讲遗留代码的书里学来的,意思是:不去判断现在的行为对不对,只把它原样记录下来。 将来重构完,只要这些测试还是绿的,就说明行为没变。

具体做法是,我从测试环境里挑了一批真实的入参样本(做了脱敏),覆盖几种结算方式和几个边界情况:金额为零、优惠券叠加、跨月账单之类。然后让 AI 帮我写测试骨架。

这里有一个很关键的细节:测试里的期望值,必须是用现在的代码真实跑出来的,不能让 AI 推算。 一开始它很自然地按“它理解的正确逻辑”填了期望值,结果有几条一跑就红——不是代码错了,是它的理解和代码的实际行为不一样。这恰恰说明,如果我让它凭理解去重构,它也会按它的理解去“改对”。

第二步:一次只用一种手法

有了安全网,我开始重构,但给自己定了一条规矩:每一步只用一种重构手法,做完就跑测试,绿了就提交。

小步重构的节奏:先锁住行为,每一步只用一种手法,测试通过就提交

图:小步重构的节奏:先锁住行为,每一步只用一种手法,测试通过就提交(原创示意图)

比如第一步只做“提取方法”,把那个两百行的方法按段落拆成几个私有方法,名字先起得朴素一点;第二步只做“改名”;第三步只做“引入参数对象”,把那七八个传来传去的参数收拢成一个对象;再往后才是“用多态取代条件分支”,把几种结算方式拆成独立的类。

每一步我给 AI 的提示都很具体,大概长这样:

只做一件事:把 calculate 方法里“计算优惠”的那一段(第 120 到 168 行)提取成一个私有方法。
不要改名,不要调整逻辑,不要修改任何看起来像 bug 的地方。
如果你发现可疑的地方,列在回复最后,不要动代码。

这样它每次给的 diff 都只有几十行,我几分钟就能看完。有一次测试红了,我直接用 git 回到上一个提交,只损失了一小步,重新来就好。

第三步:机械的活交给 IDE

做着做着我发现,有些改动其实根本不该让 AI 来做。

重构中不同类型的改动,交给 IDE、AI 还是自己

图:重构中不同类型的改动,交给 IDE、AI 还是自己(原创示意图)

改名、移动类、提取方法、修改方法签名,这些 IDE 的重构功能是基于语法分析做的,会把所有引用一起改掉,可靠性比让 AI 生成文本高得多。让 AI 改名,它可能漏掉某个反射调用的地方,也可能顺手把注释里的同名单词也改了。

AI 真正擅长的,是那些需要“判断”和“写样板”的部分:这一大段逻辑该怎么拆成几块比较自然?这几个分支的共性是什么,适不适合抽成接口?新拆出来的策略类,测试骨架怎么写?

所以后来我的分工基本是:AI 出主意、写样板,IDE 做机械变换,我负责拍板和验证。

第四步:发现的“bug”,单独处理

重构过程中,AI 和我自己都发现了几处可疑的地方。我的原则是:全部先记下来,一处都不在重构里改。

重构完成、测试全绿之后,我把这几处单独列出来,一条一条去问业务和老同事。结果有两处确实是 bug,单独开了提交修掉,各自补了测试;另外几处都有历史原因,我在代码里补了注释,写清楚为什么这么做,免得下一个人(或者下一个 AI)又想“修”它。

中间踩过的几个小坑

小步走也不是一路顺风,记几个印象比较深的。

特征测试“看着全”,其实漏了分支。 我一开始挑的样本,自以为覆盖了主要情况。后来跑了一下覆盖率工具,才发现有两个分支一次都没被走到,其中一个恰好是最绕的那段跨月逻辑。于是又回头补样本,直到我关心的分支都被至少一条测试走过。覆盖率数字本身说明不了什么,但它能告诉你“哪里完全没被看着”。

它会在“只提取方法”的时候偷偷改别的。 有一次我明确说只提取方法,它交回来的代码里,顺手把一个异常包了一层,换成了另一个异常类型。测试没红,因为测试没覆盖到那个异常路径。是我看 diff 时发现的。从那以后,我看 AI 的重构 diff,第一件事就是确认“除了我要求的那种改动,还有没有别的”。

它很喜欢“过度设计”。 有一处只有两个分支的判断,它建议我上工厂加策略加注册表,一下子多出四个类。道理上说得通,但对一个十年都没加过第三种情况的分支来说,这是给自己找麻烦。最后我只保留了一个简单的 if。AI 给的方案往往是“教科书上最完整的”,而不是“这个场景下最合适的”,取舍还得自己来。

结果

那个类最后被拆成了几个职责清楚的小类,前后用了一周多的零碎时间,攒了二十多个小提交。每一个提交单独拿出来都很无聊:提取一个方法、改一个名字、挪一个类。但正是因为每一步都无聊,每一步都能在几分钟内被看懂、被验证。

如果和第一次那份“一步到位”的 diff 比,最终代码的样子其实差不太多。区别在于,这一次我敢合,而且知道它为什么是对的。

一点体会

用 AI 写新代码,它快一点慢一点、风格怪一点,问题都不大。但重构老代码不一样,老代码里藏着很多没写在任何地方的业务约定,它们看起来就像 bug。AI 没有这些背景,越是“聪明”,越容易把它们“改对”。

AI 能做大爆炸式的重构,它生成几百行代码只要几十秒。可验证几百行改动,要花我好几个小时,还不一定验得准。重构的瓶颈从来不是写代码的速度,而是验证的速度。 把步子迈小,不是因为 AI 不行,而是因为我的验证能力有上限。

现在每次让 AI 碰老代码之前,我都会先问自己一句:如果它改坏了,我能在几分钟内发现并回退吗?答案是否定的话,就先别急着让它动手,把安全网和步子先安排好。

评论区
评论
{{comment.creator}}
{{comment.createTime}} {{comment.index}}楼
评论

让 AI 重构老代码,我宁可它一次只动一小步

让 AI 重构老代码,我宁可它一次只动一小步

我手上有个老项目,里面有一个结算相关的 Service 类,六百多行。最长的一个方法两百多行,if-else 套了五六层,中间还夹着几段被注释掉、但没人敢删的代码。每次需求要动它,大家都绕着走,能加个分支就加个分支。

有一天我终于下决心收拾它。那时候我已经用 AI 写了不少代码,心想重构这种“体力活”,交给它正合适。

结果第一次尝试,差点让我对“用 AI 重构”这件事彻底失去信心。后来换了一种做法,才算顺利做完。这篇就讲讲这两次的区别。

第一次:一句“帮我重构得清晰一点”

我把整个类贴给 AI,说:“这个类太乱了,帮我重构得清晰一点,保持功能不变。”

它给我的结果,单看确实漂亮:长方法拆成了十几个小方法,几种结算方式抽成了策略类,变量名全换成了更有意义的名字。它还很贴心地附了一段说明,说“顺便修复了几处潜在问题”,比如某个地方金额舍入的方式“不一致”,某个空值判断“遗漏了”。

然后我看了一眼 diff:几百行改动,几乎每一行都变了。

问题来了:我怎么知道它真的“功能不变”?

这个类几乎没有单元测试。它说保持功能不变,但我没法验证。那几处“顺便修复”更让我心里发毛——我去翻了一下提交记录和需求文档,其中一处舍入方式不一致,是当年为了对齐某个合作方的对账规则故意这么写的。它眼里的 bug,是业务上的约定。

那份 diff 我最后一行都没合。不是 AI 写得不好,而是验证它的成本全落在我身上,而我根本验证不过来。

问题出在哪

事后复盘,我觉得问题有三个,都不是 AI 的“智商”问题,而是我给它的任务本身就有毛病。

一是重构和行为变更混在了一起。 重构的定义就是不改变外部行为,只改内部结构。可它在重构的同时“顺手修 bug”,等于把两种性质完全不同的改动搅在一个 diff 里。一旦出问题,我根本分不清是结构改坏了,还是“修复”改坏了。

二是步子太大。 一次改几百行,哪怕每一行都对,我也没法在合理的时间里确认它们都对。人脑做代码审查是有容量上限的。

三是没有安全网。 没有测试,就没有“改完之后行为没变”的证据,只能靠肉眼和信心。

想清楚这三点,第二次的做法就自然出来了。

第二次:先锁住行为,再一步一步走

第一步:写特征测试

在动任何代码之前,我先让 AI 帮我写“特征测试”。这个概念我是从讲遗留代码的书里学来的,意思是:不去判断现在的行为对不对,只把它原样记录下来。 将来重构完,只要这些测试还是绿的,就说明行为没变。

具体做法是,我从测试环境里挑了一批真实的入参样本(做了脱敏),覆盖几种结算方式和几个边界情况:金额为零、优惠券叠加、跨月账单之类。然后让 AI 帮我写测试骨架。

这里有一个很关键的细节:测试里的期望值,必须是用现在的代码真实跑出来的,不能让 AI 推算。 一开始它很自然地按“它理解的正确逻辑”填了期望值,结果有几条一跑就红——不是代码错了,是它的理解和代码的实际行为不一样。这恰恰说明,如果我让它凭理解去重构,它也会按它的理解去“改对”。

第二步:一次只用一种手法

有了安全网,我开始重构,但给自己定了一条规矩:每一步只用一种重构手法,做完就跑测试,绿了就提交。

小步重构的节奏:先锁住行为,每一步只用一种手法,测试通过就提交

图:小步重构的节奏:先锁住行为,每一步只用一种手法,测试通过就提交(原创示意图)

比如第一步只做“提取方法”,把那个两百行的方法按段落拆成几个私有方法,名字先起得朴素一点;第二步只做“改名”;第三步只做“引入参数对象”,把那七八个传来传去的参数收拢成一个对象;再往后才是“用多态取代条件分支”,把几种结算方式拆成独立的类。

每一步我给 AI 的提示都很具体,大概长这样:

只做一件事:把 calculate 方法里“计算优惠”的那一段(第 120 到 168 行)提取成一个私有方法。
不要改名,不要调整逻辑,不要修改任何看起来像 bug 的地方。
如果你发现可疑的地方,列在回复最后,不要动代码。

这样它每次给的 diff 都只有几十行,我几分钟就能看完。有一次测试红了,我直接用 git 回到上一个提交,只损失了一小步,重新来就好。

第三步:机械的活交给 IDE

做着做着我发现,有些改动其实根本不该让 AI 来做。

重构中不同类型的改动,交给 IDE、AI 还是自己

图:重构中不同类型的改动,交给 IDE、AI 还是自己(原创示意图)

改名、移动类、提取方法、修改方法签名,这些 IDE 的重构功能是基于语法分析做的,会把所有引用一起改掉,可靠性比让 AI 生成文本高得多。让 AI 改名,它可能漏掉某个反射调用的地方,也可能顺手把注释里的同名单词也改了。

AI 真正擅长的,是那些需要“判断”和“写样板”的部分:这一大段逻辑该怎么拆成几块比较自然?这几个分支的共性是什么,适不适合抽成接口?新拆出来的策略类,测试骨架怎么写?

所以后来我的分工基本是:AI 出主意、写样板,IDE 做机械变换,我负责拍板和验证。

第四步:发现的“bug”,单独处理

重构过程中,AI 和我自己都发现了几处可疑的地方。我的原则是:全部先记下来,一处都不在重构里改。

重构完成、测试全绿之后,我把这几处单独列出来,一条一条去问业务和老同事。结果有两处确实是 bug,单独开了提交修掉,各自补了测试;另外几处都有历史原因,我在代码里补了注释,写清楚为什么这么做,免得下一个人(或者下一个 AI)又想“修”它。

中间踩过的几个小坑

小步走也不是一路顺风,记几个印象比较深的。

特征测试“看着全”,其实漏了分支。 我一开始挑的样本,自以为覆盖了主要情况。后来跑了一下覆盖率工具,才发现有两个分支一次都没被走到,其中一个恰好是最绕的那段跨月逻辑。于是又回头补样本,直到我关心的分支都被至少一条测试走过。覆盖率数字本身说明不了什么,但它能告诉你“哪里完全没被看着”。

它会在“只提取方法”的时候偷偷改别的。 有一次我明确说只提取方法,它交回来的代码里,顺手把一个异常包了一层,换成了另一个异常类型。测试没红,因为测试没覆盖到那个异常路径。是我看 diff 时发现的。从那以后,我看 AI 的重构 diff,第一件事就是确认“除了我要求的那种改动,还有没有别的”。

它很喜欢“过度设计”。 有一处只有两个分支的判断,它建议我上工厂加策略加注册表,一下子多出四个类。道理上说得通,但对一个十年都没加过第三种情况的分支来说,这是给自己找麻烦。最后我只保留了一个简单的 if。AI 给的方案往往是“教科书上最完整的”,而不是“这个场景下最合适的”,取舍还得自己来。

结果

那个类最后被拆成了几个职责清楚的小类,前后用了一周多的零碎时间,攒了二十多个小提交。每一个提交单独拿出来都很无聊:提取一个方法、改一个名字、挪一个类。但正是因为每一步都无聊,每一步都能在几分钟内被看懂、被验证。

如果和第一次那份“一步到位”的 diff 比,最终代码的样子其实差不太多。区别在于,这一次我敢合,而且知道它为什么是对的。

一点体会

用 AI 写新代码,它快一点慢一点、风格怪一点,问题都不大。但重构老代码不一样,老代码里藏着很多没写在任何地方的业务约定,它们看起来就像 bug。AI 没有这些背景,越是“聪明”,越容易把它们“改对”。

AI 能做大爆炸式的重构,它生成几百行代码只要几十秒。可验证几百行改动,要花我好几个小时,还不一定验得准。重构的瓶颈从来不是写代码的速度,而是验证的速度。 把步子迈小,不是因为 AI 不行,而是因为我的验证能力有上限。

现在每次让 AI 碰老代码之前,我都会先问自己一句:如果它改坏了,我能在几分钟内发现并回退吗?答案是否定的话,就先别急着让它动手,把安全网和步子先安排好。


让 AI 重构老代码,我宁可它一次只动一小步2026-10-11鱼鱼

{{commentTitle}}

评论   ctrl+Enter 发送评论