[控场AI]
· 6 分钟阅读· 3,165 字

老代码交给AI改,为什么先写AGENTS.md是错的?

老代码交给AI改,为什么先写AGENTS.md是错的?

老仓库交给AI前应先给测试当裁判,再让AI自己画地图。

文章提出一个反直觉的迁移顺序:把老代码库交给AI重构前,应先提供可靠的测试而非详尽的AGENTS.md。原因是Claude Code等工具在“觉得活干完了”时就会停止,测试是它唯一的退出条件和正确性裁判。地图(文件依赖关系)可由AI和确定性工具自行绘制,人只需补充项目特有的隐含要求。检查应按从便宜到贵的顺序执行,任何一关失败即丢弃改动。对没有测试的遗留代码,可让AI生成特征测试锁定现有行为。但测试全绿不等于质量达标,Burn迁移后仍出现19个语义回归,需模糊测试与安全审查补位。

把跑了十年的老仓库交给AI重构,大多数人的第一反应是写一份详尽的AGENTS.md,把每个模块的职责、架构、注意事项都交代清楚。但Kaolab在B站分享的一个反直觉观点是:这个顺序应该反过来——先给测试,再给地图。因为地图AI自己画得出来,而"改对了没有"它给不了自己。

测试是AI改代码的退出条件

Anthropic在一篇大规模代码迁移的总结中,把"先有一个靠得住的裁判"排在所有步骤之前。原因很直接:Claude Code的官方文档写明,它觉得活干完了就会停。如果没有一个能跑的检查,"看起来做完了"就是它唯一的信号——这次迁移既没有退出条件,也无法衡量成功。

反例很有说服力:Burn把53万行Zig代码交给Claude翻成100多万行Rust,合并前测试套件全部通过,包含100多万条断言(测试用TypeScript写,与实现语言无关,所以换语言不用换裁判)。可即便全绿,合并后还是冒出了19个细小的语义回归。测试覆盖率说明它没有违反测试写下来的那部分行为,没写下来的那部分,测试管不到。

看起来做完了

地图该由谁来画

地图当然要有。AIbin在迁移测试文件时自己总结的头号成功因素是:给它挑出正确相关的文件。问题在于这张地图该让谁来画。

Claude Code文档专门列了AGENTS.md不该写什么:逐个文件介绍代码库的内容不写,它读代码就能搞清楚的东西也不写。该写的是它猜不到的东西——要跑的命令、项目特有的架构决定、那些不明显的坑。至于文件之间谁引用谁,交给工具去查:Google在内部代码迁移中用代码索引顺着引用一层层往外找,Anthropic的迁移总结里依赖关系也是用确定性脚本画出来的。

人手写的那几行为什么反而值钱?METR做过一个随机对照实验:16位资深开源开发者在自己维护多年、平均超过100万行代码的大仓库里干活,用AI之前他们预计能快24%,实际结果却是比不用AI慢了19%。研究者给出的解释之一是,这类项目里有大量隐含要求——文档怎么写、测试覆盖到什么程度、格式怎么排——人要花很长时间才学得会。地图上真正缺的正是这一块,而它恰恰是AI读代码读不出来的。

这些仓库平均超过100万行代码

检查要按从便宜到贵的顺序过

Google那套迁移流程里,大模型改完的每一处都要按从便宜到贵的顺序过检查:空白、语法、语法解析、编译,最后跑回归测试。任何一关没过,这次改动直接丢掉,交给人来改。39次迁移、595个代码变更中,74%是大模型生成的。论文还记录了一个失败原因:有些测试本来就是红的,测试那一关根本跑不起来——所以测试不关要用,交给他之前还得先保证主干是绿的。

AIbin的做法同样是由检查来驱动:3500个测试文件从Enzyme迁移到React Testing Library,每个文件依次过just、lint和类型检查,哪一层报错才把报错交给大模型去修。4个小时迁完75%,又调了4天推到97%,原本估计人工要一年半,最后6周做完。他们自己的总结是:反复重试比把提示词磨到完美更管用。

just, lint

模糊测试(Fuzzing) 是一种自动化软件测试技术,通过向程序持续输入大量随机或半随机的畸形数据,观察程序是否崩溃、挂起或产生异常行为,从而发现边界条件和未处理输入导致的缺陷。它与单元测试的互补性在于:单元测试验证的是开发者“想到并写下来”的场景,而模糊测试能覆盖人意料之外的输入组合。实践中常配合覆盖率引导(coverage-guided)策略,让变异器优先探索尚未被覆盖的代码路径。文中所说的“不间断的模糊测试”,即持续集成中长时间运行的 fuzz 任务,适合在 AI 生成的大规模代码上补足传统测试覆盖不到的安全与健壮性盲区。

老代码没有测试怎么办

最难的情况是老代码根本没有测试。Michael Feathers在《修改代码的艺术》(Working Effectively with Legacy Code)里给遗留代码下的定义就是"没有测试的代码"。他的方法是:先找到要改的点,再找能测的点,解开依赖,写好测试,最后才去改代码。这时写的测试叫特征测试(characterization test)——它不判断代码对不对,只把当前行为原样记下来,连bug一起锁住。

这一步现在可以交给AI。Anthropic那篇总结的原话是:继承不到裁判,就让Claude造一个,原来的代码库永远是标准答案。Mike Krieger把165,000行Python迁移到TypeScript时手上没有现成测试,就让Claude写了个脚本,拿7个真实场景在新旧两边一起跑、对比输出,一直修到7个全部通过:裁判到位了,还要故意把代码改坏,确认它真的会红。

就让Cloud写了个脚本

特征测试(Characterization Test) 是 Michael Feathers 在《修改代码的艺术》中提出的概念,指在修改遗留代码前编写的、用于锁定代码当前实际行为的测试。与常规测试不同,它不以“正确性”为标准——即使代码存在 bug,测试也会把 bug 的行为原样断言下来,目的是建立一张“行为快照”,使后续重构或迁移一旦无意改变现有行为就能立刻被发现。编写特征测试的典型流程是:在代码中寻找可观测的输出点(返回值、日志、副作用),为其编写断言,然后运行并记录实际结果作为期望值。由于它只关心“是否变化”而非“是否正确”,非常适合作为 AI 迁移过程中的裁判:旧系统即为标准答案,新实现必须逐字节对齐旧行为。

测试是AI的退出条件,不是质量的证明

回到Burn那19个回归。Zig语言的作者Andrew Kelley曾公开质疑,这套测试够不够兜住100万行没人逐行评审过的代码。Burn后来做了11轮安全审查,给每个解析器挂上不间断的模糊测试,又修出了15个PR。这说明即使测试全绿,也不能把"通过测试"等同于"质量达标"。

所以老代码库交给AI之前,先问三件事:有没有一条命令能告诉他改对了没有?这条命令现在主干上是不是全绿的?故意改坏一处,它会不会变红?三个都是,再让他自己去画地图——你只管去补那几行他从代码里读不出来的东西。

核心要点:

  • 测试是AI改代码的退出条件,决定它何时停、改得对不对
  • 地图该由AI画,人只需补它读不出来的项目隐含要求
  • 检查按从便宜到贵的顺序过,任何一关失败直接丢弃改动
  • 老代码没测试时,让AI生成特征测试,把现有行为连bug一起锁住
  • 全绿不等于质量达标,模糊测试与安全审查仍需补位

背景补充

语义回归(Semantic Regression) 指代码在语法、编译和既有测试全部通过的情况下,行为却在语义层面发生了细微而真实的改变。这类问题难以被测试捕获,因为测试只断言了编写者显式写下的那部分行为契约;未被测试覆盖的语义——例如浮点精度处理、空值边界、错误码映射、迭代顺序——一旦在重写中悄悄改变,就可能在下游引发难以定位的故障。这也是“全绿不等于质量达标”的技术根源:测试覆盖率本质上是“已写断言的行为占比”,而非“全部行为的正确性证明”。因此大规模自动迁移后,通常需要补充差分测试、模糊测试与人工审查来兜住语义层面的遗漏。

#AI代码迁移#AGENTS.md#测试驱动开发#特征测试#Claude Code#遗留代码重构#语义回归
分享:

相关推荐