跳到主要内容

大规模 AI 辅助代码库迁移:自动化处理那些没人想碰的升级

· 阅读需 13 分钟
Tian Pan
Software Engineer

当 Airbnb 需要将 3,500 个 React 测试文件从 Enzyme 迁移到 React Testing Library 时,他们估计该项目需要 1.5 年的人力。通过使用 LLM 驱动的流水线,他们仅用 6 周就完成了交付。当 Google 研究了一个由 3 名开发人员在 12 个月内执行的 39 次不同代码迁移(595 次代码更改,93,574 次编辑)时,他们发现 74% 的编辑是由 AI 生成的,其中 87% 的编辑在没有人工修改的情况下就被提交了,整体迁移时间缩短了 50%。

这些数字是真实的。但这也是事实:在这些迁移过程中,工程师花费了大约 50% 的时间来验证 AI 的输出——修复上下文窗口故障、清理幻觉生成的导入,以及理顺测试未能捕捉到的业务逻辑错误。效率的提升是真实的,痛点也是真实的。问题不在于 AI 是否属于代码迁移;而在于准确了解它在何处提供帮助,以及在何处创造的清理工作超过了它所节省的时间。

你需要的两种截然不同的工具

大多数团队犯的第一个错误是将“AI 辅助迁移”视为单一类别。实际上有两个不同的工具家族,它们各自擅长不同的事情。

基于 AST 的 codemods (ast-grep, jscodeshift, GritQL, OpenRewrite, Comby) 在语法树级别工作。它们是确定性的——相同的输入始终产生相同的输出——并且可以完美扩展。ast-grep 可以在几秒钟内搜索并转换多语言代码库中的数百万行代码。OpenRewrite 拥有 5,000 多个用于 Java、Python、YAML、Terraform 和 Kubernetes 迁移的配方。这些工具不会产生幻觉。它们也不理解你的代码在做什么——它们转换的是结构,而不是语义。

基于 LLM 的智能体 (Claude Code, Copilot, 基于前沿模型构建的自定义流水线) 理解语义。它们可以将回调风格的 API 迁移到基于 promise 的 API,同时保留周围业务逻辑的意图。它们可以阅读内联注释和编码风格,并生成符合代码库惯例的代码。它们有时也会发明不存在的导入,对相同的提示词产生非确定性的输出,并在代码库超过其上下文窗口时静默失败。

实际结论:使用 AST 工具进行检测和结构转换,使用 LLM 进行需要理解含义的语义转换。混合方法——使用确定性引擎进行模式匹配,使用 LLM 重写匹配的部分——结合了前者的可靠性和后者的上下文智能。

AI 迁移在哪些方面确实快了 10 倍

并非所有迁移都是平等的。某些任务类别的特征使得 AI 辅助显然是正确的选择:

具有明确定义转换规则的机械式 API 替换。 React 已弃用的 componentWillMountcomponentDidMount 转换。React Testing Library 对 Enzyme 的 .find().simulate() 的模拟。Next.js 用于 Pages Router → App Router 迁移的 codemods。这些都有记录在案的转换模式,新的 API 表面已被充分理解,并且测试能立即告诉你转换是否正确。AI 在这些方面能取得成功,是因为映射关系清晰且测试信号紧密。

测试框架迁移。 Airbnb 涉及 3,500 个文件的 Enzyme 迁移就是典型的例子。测试文件很少包含 LLM 需要仔细推理的业务逻辑——它们包含遵循可预测模式的测试设置、断言和 mock。输出质量很高,失败模式是隔离的(损坏的测试不会破坏生产环境),并且测试套件本身就是验证机制。

清洁业务逻辑上的语言版本演进。 Python 2 → 3,Java 8 → 17,TypeScript 严格模式采用。当代码是算法性的——主要是数据转换、实用函数、没有复杂基础设施的领域逻辑时——在 80–90% 的文件上实现无需人工干预的正确性是现实的。剩下的 10–20% 是需要人工审查的边缘情况,而不是会传播的结构性故障。

批量机械重构。 在 4,000 个文件中重命名已弃用的符号。移动导入。在启用 ESLint 之前规范化遗留代码库中的空格和格式。这些任务具有足够的确定性,即使是纯粹的 codemods 也能很好地处理——LLM 的作用较小,相应的幻觉风险也较低。

AI 迁移在哪些方面创造的工作量超过了节省的工作量

失败模式集中在四个类别:

复杂的架构重构。 从单体服务转向模块化架构。将一个上帝类 (God class) 拆分为适当的领域对象。重组多模块依赖。LLM 在跨类推理方面表现极差。它们缺乏理解模块之间隐含依赖关系所需的全局代码库上下文,并且往往生成的代码在局部是一致的,但在全局是不一致的。这里的清理工作很容易超过规范的人工方法所花费的成本。

具有特定领域限制的迁移。 舍入行为具有监管影响的财务计算。业务规则嵌入在代码中而非文档中的保险承保逻辑。行为语义由规范驱动的医疗设备软件。AI 可以正确转换表面语法,同时微妙地改变语义,而单元测试无法捕捉到这些变化——并且这些问题可能直到客户投诉或审计时才会浮出水面。

不进行重新设计的大规模架构重写。 将一个 20 万行的 Delphi 应用程序逐行翻译成 C# 并不能产生一个 C# 应用程序——它产生的是一个在错误的运行时上编译的 Delphi 应用程序。AI 智能体可以准确地翻译代码语法和模式,但它们无法修复结构性问题。如果你在迁移时不重新思考架构,你就是在自动化地延续原始的设计错误,而清理成本会在以后出现。

缺少类型信息的代码库。 无类型的 JavaScript、没有注解的 Python、没有文档的遗留 C 语言。当代码没有明确类型和关系时,LLM 会幻觉出这些信息。一个幻觉出的类型会产生级联效应:一个虚构的接口定义导致调用方传递错误的形状,从而导致生产环境中的序列化错误,最终导致在一个看似与迁移完全无关的系统中发生故障。

确保安全性的验证策略

无论你使用哪种工具,有一个原则是不可逾越的:在人工审阅 Diff 之前,每个迁移的文件都必须通过其现有的测试套件。

Google 的生产流水线强制执行这一点。变更在开发者审阅之前通过 CI/CD 进行验证——开发者只会看到已经通过构建和测试的变更。这把审阅的工作量从“这正确吗?”转变为“是否有测试未覆盖到的地方?”。这是一个更小的认知任务,也是“人机回环”模式的正确用法。

测试关卡对你如何安排迁移顺序有明确的影响:你无法在测试覆盖率低的代​​码库上安全地运行大规模 AI 迁移。 在尝试 3,000 个文件的迁移之前,你需要知道哪些文件具有实质性的测试覆盖,哪些没有。有覆盖的文件进入自动化流水线。没有覆盖的文件则需要手动迁移,或采用测试先行(test-first)的手动迁移——即先编写测试,再迁移,最后验证。如果不加区分地混合处理,会产生虚假信号:流水线报告 3,000 个文件已迁移且测试通过,但你却在 400 个没有测试的文件中悄无声息地引入了行为退化。

对于生产系统,单元测试之外的验证层也非常重要。契约测试(Contract testing)可以验证重构后的代码在其他服务依赖其 API 行为时是否符合接口预期。跨家族 LLM 裁判(使用不同的大模型家族来审阅 AI 生成的 Diff)可以捕捉到原始模型无法识别出的幻觉。

管理 Diff 审查瓶颈

第二个关键约束是审查瓶颈。AI Agent 可以在几秒钟内生成涉及 40 个文件、2,000 行的 Diff。你的审阅者无法以同样的速度进行审查。

以下三种模式会有所帮助:

具有明确边界的堆叠式 PR(Stacked PRs)。 将迁移拆分为具有逻辑边界的批次——按模块、按文件类型或按转换类别。标题为“迁移 auth 模块:23 个文件,847 行”的 PR 是可审阅的。而标题为“迁移所有内容:3,241 个文件”的 PR 则不可审阅。创建更多 PR 的开销是真实存在的,但这种开销小于对没人真正审阅过的 Diff 进行“橡皮图章式审批”的代价。

基于风险的审查分层。 并非所有变更都需要同等程度的仔细检查。如果是一个纯粹的结构性转换,比如在 200 个文件中重命名某个导入,且经过了 CI 流水线验证,并由 codemod 确定性地生成——这只需要抽查,而不是逐行审阅。而由 LLM 生成的支付处理流程重写,则需要全面的审阅。请将这种区别明确地构建到你的审查流程中。

AI 辅助的 Diff 摘要。 在人工审查开始之前,使用第二次 LLM 处理来生成变更内容及其原因的执行摘要。这不能取代审查,但它能在审阅者进入“领地”之前为他们提供一份“地图”。采用这种做法的团队报告称,处理大型 Diff 时的认知负荷显著降低,且反馈质量更高,因为审阅者花费在理解结构上的时间更少,而花费在评估风险上的时间更多。

不破坏构建的增量迁移

“一次性迁移所有内容”是将一个 6 周的项目变成 6 个月项目的最常见方式。行业标准的替代方案是“扩展-迁移-收缩”(expand-migrate-contract)模式:

  1. 扩展 (Expand):在旧接口旁边添加新接口。两者并存,不破坏任何功能。
  2. 迁移 (Migrate):将消费者移动到新接口。在验证新接口工作正常后,停用每个消费者的旧接口。
  3. 收缩 (Contract):当旧接口没有剩余消费者时,将其移除。

这意味着在一段时间内同时运行新旧代码。开销是真实存在的,但替代方案——硬切换(hard cutover)——意味着你的迁移要么完全成功,要么完全回滚。这种二元对立会产生压力,迫使人们为了进度而推送未经充分测试的变更。

特性标志(Feature flags)将这种模式扩展到了生产发布。新迁移的代码存在于构建中,但通过配置为特定比例的流量或特定的用户群体激活。你可以监控行为差异,捕捉测试遗漏的边缘案例,并在不撤回整个部署的情况下回滚特定用户。

专门针对数据模式(schema)迁移:将代码迁移与数据迁移完全分离。先迁移代码,使其能针对旧模式和新模式正确运行。然后迁移数据。最后移除向后兼容层。永远不要同时进行这三者。

任务分类决策

在开始迁移之前,先对其进行分类:

  • 高置信度(AI 优先):具有官方 codemod 支持且文档齐全的框架版本升级、测试框架迁移、批量符号重命名、强类型代码上的导入重组。
  • 中置信度(AI 配合严格审查):具有语义复杂性的 API 模式迁移、混合质量遗留代码上的语言版本演进、非类型化语言中的纯业务逻辑。
  • 低置信度(人工优先或仅限人工):架构重组、带有监管约束的领域逻辑、测试覆盖率 <50% 的代码库、需要重新设计的迁移。

对于高置信度迁移,ROI(投资回报率)非常出色。对于低置信度迁移,ROI 可能是负的——验证开销、幻觉清理和审阅负担可能会超过手动操作的成本,同时产出的质量更低。

最真实反映这种划分的数据来自 Google 的研究:87% 的 AI 生成代码在没有人工修改的情况下被提交。这听起来像是近乎完美的准确率。确实如此,但这仅针对 Google 认为适合“AI 优先”自动化的迁移。对于那些他们选择不进行自动化的迁移,这个数字并不能说明任何问题。

规模化带来的实际变化

那些从 AI 辅助迁移中获益最多的团队,通常具有一些共同的运作模式。

他们在迁移之前会先在工具开发上投入。一个调优良好的 codemod 或 prompt,如果在 100 个文件上能产生 90% 的正确输出,那么在 10,000 个文件上同样能保持 90% 的正确率。而一个调优欠佳、需要对 30% 的文件进行清理的工具,在规模化之后会导致无法承受的清理工作量。这种调优上的投入会带来成倍的回报。

他们会追踪双方的实际成本。每工程师小时迁移的文件数量是显而易见的,但每千个迁移文件所需的清理时间通常未被追踪——它往往作为“代码审查(reviewing PRs)”被吸收进 sprint 容量中。让清理成本可见,是准确做出“自建工具 vs 人工手动”决策的前提条件。

他们承认,即使 50% 的工程师时间花在验证上,这依然是一笔划算的交易。Google 的工程师曾花费一半的时间来验证 AI 输出。这听起来很糟糕,直到你将其与另一种情况对比:同样的工程师花费 100% 的时间进行手动迁移。加速器的本质并不是消除人工参与,而是将人的角色从“编写代码”转变为“评判代码”。后者速度更快,并且可以基于已通过验证的 CI 结果异步进行。

AI 目前尚无法完成的迁移——涉及架构变动的、语义复杂的、文档匮乏的遗留系统——随着时间的推移并不会变得更容易。但 AI 能够完成的那些迁移,正在变得更快、更便宜且更可靠。实际的策略是清晰地捕捉收益,诚实地衡量成本,并将人力资源留给那些仍然需要人类处理的迁移类别。


如果你发现这篇文章有用,关于 Agent 幂等性结构化输出可靠性 的配套文章涵盖了生产环境 AI 系统中的相关挑战。

References:Let's stay in touch and Follow me for more thoughts and updates