用 Agent 做大重构
系列入口:如何使用 Agent。前置:Git 快照与回滚、用 Agent 调试。
一、重构的铁律与 Agent 的关系
重构有一条不可违背的定义:在不改变外部可观察行为的前提下,改善内部结构。 「行为保持」是重构与改功能的根本分界。Agent 能以极快速度搬动、拆分、重命名大量代码,让原本让人望而却步的大重构变得可行;但同样因为快,一旦它在重构中悄悄改变了行为、又没有测试兜住,错误会被大规模铺开且极难察觉。
所以用 Agent 做重构,核心不是让它「一次改完」,而是设计一套让任何行为偏差都能立刻暴露的节奏:安全网先行、分模块、小步走、每步验证。
二、第一步:确认或建立测试安全网
在让 Agent 搬动代码前,先问一个决定性问题:如果它改坏了行为,我能立刻知道吗?
如果相关模块已有可信、覆盖关键路径的测试,这就是现成的安全网,重构中每一步都靠它确认行为仍在。如果是缺乏测试的遗留代码,直接让 Agent 大改等于盲飞——改没改坏根本无从判断。这时应先补特征测试(characterization tests):不追求验证「理想设计」,而是把代码当前的实际行为(包括一些看似古怪的既有行为)用测试固定下来,作为重构期间的对照基准。
这一步看似额外,却是大重构成败的分水岭。有一张能抓住行为偏差的网,你才敢让 Agent 放手调整结构;没有它,再快的重构也是在赌。 让 Agent 帮忙阅读代码、生成覆盖现状的特征测试,往往是它很擅长的切入点。
三、分模块、小步走
大重构最忌讳「一口气全改」。正确做法是把目标拆成一个个模块或一个个独立的小变换,逐个推进。
小步意味着每一步都是一个简单、可理解、单独可验证的改动:一次重命名、一次提取函数、一次移动模块。每完成一小步,立刻运行测试,确认仍为绿,再进入下一步。这样一旦某步引入行为偏差,能立刻定位到「就是这一小步造成的」,回退也只需退回最近的检查点,而不是在几千行混杂改动里排查。
Agent 特别适合执行这种机械、模式化的小变换,但你要约束它的节奏:不要让它为了「高效」把提取、重命名、改逻辑揉成一个巨型改动。配合 Git 检查点,每个绿的小步都可以提交成落脚点。
四、行为保持:绝不在重构中途改功能
这是重构中最常被打破、也最危险的一条边界。
当你或 Agent 在重构时看到一段「写得不好、顺便改一下」的逻辑,很容易顺手把它的行为也改了。一旦结构调整和行为变化混在一起,你就失去了判断基准:测试红了,到底是搬代码搬错了,还是你故意改功能造成的?两者纠缠,排查极其困难,而「顺便改」的东西往往没有对应的新需求和测试。
纪律是:重构阶段只调整结构、保持行为,看到想改的功能先记下来,等重构完成、行为锁定为绿后,再作为一个独立的功能变更去做。 这条隔离让「结构改善」始终可被测试干净地验证,是 Agent 大重构不翻车的关键。
五、善用编译器与测试的双重反馈
静态类型语言里,编译器是第一道极快的反馈:重命名、移动后,编译不过的地方立刻暴露引用断点。让 Agent 先依赖编译器修平所有引用,再跑测试确认运行时行为。动态语言则更依赖测试和搜索,因为很多引用错误要到运行才暴露,因此小步和测试覆盖要更谨慎。
把「编译通过」和「测试通过」分开看待:前者保证结构连得上,后者保证行为没变。两者都绿,这一小步才算真正安全。
六、如何审查一次大重构
重构完成后,改动可能很大,审查要有重点,而不是逐行纠结。
第一,确认测试在整个过程中持续为绿、关键路径都被覆盖,而不只是最后一次性跑通。第二,看 diff 里是否混入了行为变化——重构提交应当主要是移动、重命名、提取,出现条件分支或取值变化要格外警惕,确认那不是违规的「顺手改」。第三,检查模块边界和依赖是否真的朝目标改善了。把重构与功能变更拆成不同提交,也能让评审一眼看清每步意图。
七、什么时候该停
- 发现测试无法信任、或关键行为根本没有覆盖,先停下补安全网。
- 一小步之后测试变红且短时间说不清原因,退回上一个绿点重来,而不是带病前进。
- 改动已经大到你无法理解和验证整体,缩小范围、或把大目标拆成多次独立重构。
七点五、遗留代码没有测试,到底从哪下手
这是最普遍的现实困境:想重构,但代码零测试、还不敢乱改。很多人因此要么彻底放弃、要么闭眼让 Agent 硬改,两种都很糟。这里给一条可操作的破局路径。
关键工具是特征测试。它和普通单元测试的目标不同——不是验证代码「应该怎样」,而是客观记录它「现在实际怎样」。 做法是让 Agent 先帮你读懂一段代码,挑出几个有代表性的输入,实际运行、观察真实输出,再把这些「输入→实际输出」写成测试。哪怕某些行为看起来古怪、甚至像 bug,在没和需求确认前,也先如实固定下来——因为重构要保持的就是这个现状,贸然「纠正」反而改变了行为。
有了这张覆盖关键路径的特征测试网,你才第一次拥有了「改坏能立刻知道」的能力,随后才谈得上小步重构。顺序一定是先 characterize、再 refactor,绝不能反过来。 如果某块代码实在难以输入输出(强耦合、依赖一堆外部环境),那往往提示第一步应是让 Agent 做一点「为可测而做的解耦」——但那一步要格外小、格外谨慎,因为它本身还没有测试保护。
七点六、「纯重构」和「改功能」为何必须严格分开
这条前面点到,值得展开,因为它最容易在执行中被无意打破。
人在重构时看到糟糕代码会有强烈的「顺手修掉」冲动,Agent 也类似——它可能在搬移函数的同时,把一个它认为更合理的判断逻辑一并换了。一旦结构改动和行为变化混在同一个步骤,测试一红,你立刻失去判断基准:是机械搬移出错了,还是那个「顺手优化」改变了预期?两者纠缠,排查成本陡增,而顺手改的部分往往既无需求依据、也无专门测试。
纪律是把两类工作切成不重叠的阶段和提交: 重构提交里只允许出现移动、重命名、提取、合并等保持行为的变换,diff 中不应冒出新的条件分支或取值变化;看到想改的行为,记到清单里,等结构改善完、测试稳定为绿,再作为一个带新需求、新测试的独立变更处理。这个隔离让每个阶段都能被干净验证,是大重构可控的核心。
七点七、大重构做到一半,如何判断该不该继续
长周期重构常出现「越改越深、看不到头」的局面,此时需要冷静的止损判断,而不是硬着头皮冲。
几个明确的「该暂停」信号:一是发现安全网不可靠——某些所谓绿的测试其实没覆盖关键行为,继续改等于裸奔,应停下补测试;二是一小步之后变红、且短时间说不清原因,正确动作是立即退回上一个绿点,而不是带病向前累积不确定性;三是改动范围已经膨胀到你无法理解和验证整体,这说明拆分不够,应把大目标再切成几次更小、可独立交付的重构,而不是让一个巨型变更失控。
健康的心态是把大重构看作一连串各自完整、可停下来的小重构,每一步结束系统都处于可用状态。这样即使中途需求变化、时间不够,你也保留了已完成部分的价值,而不会被一个「要么全成、要么全废」的大改动绑架。能随时安全停下,恰恰是小步重构最被低估的好处。
七点八、利用类型系统,让 Agent 的重构更安全
在静态类型语言里,有一个能显著降低重构风险的利器值得专门强调:让编译器当第一道关卡。
重命名、移动模块、改变函数签名这类操作,最容易留下没改全的引用。人工搜替换难免遗漏,而类型系统会在编译时把所有断裂点逐个报出来。让 Agent 先做完机械变换,再依靠编译器把引用修平——这一步反馈极快、确定性高,比纯靠肉眼和搜索可靠得多。等编译全过,再跑测试确认运行时行为没变,构成「编译 + 测试」双保险。
这也带来一个策略含义:在类型覆盖好的代码里,你可以更放心地让 Agent 做跨多处的重命名和接口调整,因为有编译器兜底;而在动态类型语言里,这类操作要拆得更碎、步子更小,因为引用错误往往要到运行才暴露。根据语言的反馈机制调整小步的尺度,是务实的重构安排。
七点九、重构完成后,怎么审一份巨大的 diff
大重构的改动量往往很大,逐行审既不现实、也抓不到重点,这里给一个有针对性的审查方法。
第一看测试轨迹:确认测试是在每一步都持续为绿、且覆盖关键路径,而不是最后才一次性跑通——前者能证明行为全程被保持,后者可能掩盖中途的偏差。第二看改动的性质:重构提交应以移动、重命名、提取为主,用 diff 的统计和抽样确认没有混入条件逻辑或取值的变化;任何行为性改动都该在独立提交里、配独立说明。第三看结果:模块边界、依赖方向是否真的朝预设目标改善了,而不是换了个地方继续纠缠。
把重构与功能变更拆成不同提交,是让这种审查可行的前提——评审者能清楚分辨每个提交的意图,而不必在一锅大杂烩里猜。审重构,审的是「行为是否被严格保持、结构是否确实变好」,而不是每一行文字。
七点十、重构前先和团队对齐目标与边界
大重构很少是纯个人事务,尤其在共享代码库里,动手前的对齐能避免大量返工和冲突。
先对齐的是目标:这次重构要解决什么——降低耦合、拆分过大的模块、统一接口,还是为后续功能铺路?目标清晰,你才知道何时算完成,也能约束 Agent 不顺势做无关的「美化」。再对齐的是边界与节奏:涉及哪些模块、是否会影响他人正在改的代码、按什么顺序推进、如何与并行的开发错开,以及哪些对外接口在本阶段绝不能动。
把重构拆成小步、每步独立可合并,也能减少与团队工作的长期冲突;配合明确的检查点,中途需要协调时系统始终可用。先沟通清楚「为什么、改哪里、怎么分步」,再让 Agent 提速执行,大重构才不会在代码之外又制造协作上的麻烦。
八、常见问题
Q:没有任何测试,还能用 Agent 重构吗? 能,但必须先让 Agent 协助补特征测试、把现状行为锁住,再动手;跳过这一步的大重构基本不可控。
Q:重构时发现真 bug 怎么办? 记录下来,先保持当前行为完成重构;bug 的修复作为独立变更,配独立测试,避免与结构调整混杂。
Q:让 Agent 一次改完不是更快吗? 表面快,实际一旦出错,在巨型改动里定位和回退的成本远超小步推进;小步的每步验证反而让整体更稳、更快收尾。
Q:重构后测试都过了,就万无一失吗? 测试覆盖不到的行为仍可能变化,因此要确认测试确实覆盖了关键路径和边界,必要时人工核对重要场景。
Q:重构范围总是越改越大,怎么控制? 动手前写明确的目标和「本次不动」清单,每一步只做计划内的小变换;发现新问题先记到清单、另开变更,而不是当场扩大范围。让范围由目标而非冲动决定。
九、重构检查清单
- 已有可信测试网,或已先用特征测试锁定现状行为。
- 重构按模块拆分,一次只做一个可验证的小变换。
- 每一步编译通过、测试为绿,并提交成检查点。
- 全程只改结构、不改变行为,未顺手改功能。
- 重构与功能修复拆成独立提交。
- 最终审查确认无行为混入、模块边界确实改善。
总结
用 Agent 做大重构靠纪律而非胆量:先用现有测试或特征测试建立能抓住行为偏差的安全网,再分模块、小步推进,每步编译与测试双绿并打检查点,全程坚持只动结构、不改行为,把发现的功能问题留作独立变更,最后重点审查是否混入行为变化——让 Agent 的速度运行在一张始终可验证、可回退的轨道上。