生成式 AI 驱动的工作流程使 Google 能够更快地迁移代码并更有效地维护其代码库
在过去的几十年里,源代码库呈指数级增长。谷歌的 monorepo 就是一个大型代码数据集的例子,其中包含数十亿行代码。在这个庞大的代码库中,跟上代码转换(称为“迁移”)以适应新的语言版本、框架更新、不断变化的 API 和数据类型至少可以说是一项挑战。
多年来,Google 一直采用专门的基础设施来进行大规模变更,以执行复杂的代码迁移。该基础设施使用静态分析和Kythe和Code Search等工具来发现哪些位置需要更改及其依赖项。然后使用ClangMR等工具进行更改。
这种方法非常适合结构统一且边缘情况有限的更改。然而,在迁移具有复杂结构的代码时,静态分析和简单的迁移脚本会遇到限制——例如更改多个组件及其依赖项之间的接口及其用法或更新其测试。
在这篇文章中,我们描述了我们的内部方法,即将多个 AI 驱动的任务组合到一个新工具中,供 Google 开发人员使用,帮助他们大规模完成代码迁移。目标是协助工程师,让他们专注于迁移的复杂方面,而不会将他们与流程隔离开来。我们的案例研究表明,我们的方法可以成功生成迁移所需的大部分新代码,并显著减少工作中的人力劳动。
变更创建工作流程
对于代码迁移,我们构建了一个新的补充工具包来解决标准工具难以应对的变化,并将受益于机器学习(ML)模型适应周围代码的能力。
我们从概念上将迁移过程分为三个阶段:
确定代码库中需要修改的位置
编辑生成和验证
变更审查和推出
虽然每个阶段都受益于人工智能,但我们重点关注的是#2。
为了生成和验证代码更改,我们利用了针对内部 Google 代码和数据进行了微调的Gemini模型版本。
每次迁移都需要输入:
一组文件和预期更改的位置:文件中的路径 + 行号
一到两个描述变化的提示
[可选] 通过少量样本来决定文件是否真的需要迁移
多阶段代码迁移过程的示例执行。
用户提供的文件位置是通过预先存在的静态工具和人工输入的组合来收集的。我们的迁移工具包会自动扩展此集合,添加其他相关文件,包括:测试文件、接口文件和其他依赖项。此步骤尚未由 AI 驱动,但使用符号交叉引用信息。
在许多情况下,用户提供的要迁移的文件集并不完美。由于筛选输入列表可能很麻烦,因此某些文件已被部分或全部迁移的情况并不罕见。因此,为了避免在编辑生成期间进行冗余更改或混淆模型,我们为模型提供了少量样本,并要求它预测文件是否需要迁移。
我们发现,编辑生成和验证步骤是自动化系统带来的最大好处。我们的模型是按照DIDACT 方法基于 Google monorepo和流程中的数据进行训练的。在推理时,我们会用自然语言指令以及模型的一般指令注释我们预计需要更改的每一行。在每个模型查询中,输入上下文可以包含一个或多个相互关联的文件。
然后,模型会预测文件之间需要更改的差异( diff ),还可以更改相关部分,以确保最终的代码正确。
最后一项功能对于提高迁移速度至关重要,因为生成的更改可能与请求的初始位置不一致,但它们将解决意图。这减少了手动查找需要更改的整套行的需求,与基于抽象语法树修改的纯确定性更改生成相比,这是一个巨大的进步。
在上面的例子中,我们提示模型仅更新 类型必须更改的类的构造函数。在预测的统一差异中,模型还正确地修复了类中的私有字段和用法。
根据输入上下文,不同的提示组合会产生不同的结果。在某些情况下,提供太多可能发生变化的位置会导致性能下降,而不是仅在文件中的一个位置指定更改并提示模型将更改全局应用于文件。
当我们对数十个甚至数百个文件应用更改时,我们会实施一种机制,该机制会生成提示组合,并针对每个文件组并行尝试这些组合。这类似于pass@k策略,其中我们修改提示策略而不是推理温度。
我们会自动验证结果更改。验证是可配置的,通常取决于迁移。两种最常见的验证是编译更改的文件并运行其单元测试。每个失败的验证步骤都可以选择运行由 ML 支持的“修复”。该模型还针对大量失败的构建和测试进行了训练,并与差异配对,然后对其进行了修复。对于我们遇到的每个构建/测试失败,我们都会向模型提示更改的文件、构建/测试错误以及请求修复的提示。通过这种方法,我们观察到在相当多的情况下,模型能够修复代码。
当我们为每个文件组生成多个更改时,我们会根据验证对它们进行评分,并最终决定将哪组更改传播回最终更改列表(类似于Git中的拉取请求)。
案例研究:将整数从 32 位迁移到 64 位
随着 Google 代码库及其产品的发展,过去(有时是十多年前)做出的假设不再成立。例如,Google Ads 有数十种用作句柄的数字唯一“ID”类型(用于用户、商家、广告系列等),这些 ID 最初定义为 32 位整数。但随着 ID 数量的当前增长,我们预计它们会比预期更快地溢出 32 位容量。
这一认识导致我们付出了巨大努力将这些 ID 移植到 64 位整数。该项目之所以困难,原因如下:
在数千个文件中有数万个位置使用了这些 ID。
如果每个团队都自己处理数据迁移,那么跟踪所有参与团队的变化将非常困难。
ID 通常被定义为通用数字(int32_t在 C++ 或IntegerJava 中),并且不是唯一的、易于搜索的类型,这使得通过静态工具查找它们的过程变得并不简单。
需要跨多个文件考虑类接口的变化。
需要更新测试以验证 64 位 ID 是否被正确处理。
如果手动完成,预计全部工作将需要很多年的软件工程经验。
为了加快工作进度,我们使用了 AI 迁移工具并设计了以下工作流程:
一位专家工程师确定他们想要迁移的 ID,并结合使用代码搜索、Kythe 和自定义脚本来确定要迁移的文件和位置的(相对紧密的)超集。
迁移工具包可自动运行并生成经过验证的更改,这些更改仅包含通过单元测试的代码。一些测试本身也会更新以反映新的实际情况。
工程师快速检查变更,并可能更新模型失败或出错的文件。然后,这些变更被分片并发送给负责受变更影响的代码库部分的多名审阅者。
请注意,内部代码库中使用的 ID 已应用了适当的隐私保护。虽然模型将它们迁移到新类型,但不会改变或显示它们,因此所有隐私保护都将保持不变。
对于此工作流,我们发现已登陆 CL 中的 80% 代码修改是由 AI 编写的,其余的则由人类编写。据负责迁移的工程师报告,迁移所花费的总时间减少了约 50%。由于一名工程师就可以生成所有必要的更改,因此通信开销显著减少。工程师仍然需要花时间分析需要更改的文件并进行审查。我们发现,在 Java 文件中,我们的模型预测需要编辑文件的准确率为 91%。
该工具包已用于在本次和其他迁移中创建数百个变更列表。平均而言,我们实现了 75% 以上的 AI 生成字符变更成功进入 monorepo。
未来方向
下一步是解决更复杂的迁移问题,这些问题会影响多个组件交换数据或需要更改系统架构。我们在从需要进行重大重构的弃用类型迁移以及摆脱旧测试框架方面已经取得了成功。
我们正在研究如何在开发过程的其他部分应用人工智能,特别是帮助确定变更目标并更好地过滤不必要的变更。另一个有趣的领域是改善 IDE 中的迁移用户体验,以便变更操作员能够更好地控制和自由地混合搭配现有工具。
总的来说,我们看到这项工作的广泛潜在应用,可能超出严格的代码迁移空间,也可能应用于大规模错误纠正和一般代码维护。
致谢
这项工作是 Google 核心开发人员、Google Ads 和 Google DeepMind 团队通力合作的成果。我们要感谢主要贡献者 Daniele Codecasa、Anna Sjövall、Ayoub Kachkach、Celal Ziftci、Max Kim、Jonathan Binghan、Ballie Sandhu 和 Christoph Grotz。我们还要感谢我们的同事 Alexander Frömmgen、Lera Kharatyan、Maxim Tabachnyk、Shardul Natu、Bar Karapetov、Kashmira Phalak、Andrew Villadsen、Maia Deutsch、AK Kulkarni、Satish Chandra、Danny Tarlow、Aditya Kini、Marc Brockschmidt、Yurun Shen、Milad Hashemi、Chris Gorgolewski、Don Schwarz、Chris Kennely、Sarah Drasner、Niranjan Tulpule、Madhura Dudhgaonkar 以及 DIDACT 项目的开发 人员。
评论