AI Pulse

Uber用AI自动清理过期的功能开关代码

Uber用AI自动清理过期的功能开关代码

用AI删除代码:Uber的智能体技术债务清理

引言

在Uber,功能开关(动态布尔参数)是我们交付软件的核心机制。它们让工程师能够逐步推出新功能、运行实验,并在出现问题时快速回滚。如今,我们在移动应用和后端服务中管理着数千个功能开关,为全球数百万用户提供支持。

功能开关的一个问题是:一旦功能完全推出,遗留的脚手架代码(如条件分支和测试)往往会被留在原地,悄然变成技术债务。随着过期标志的累积,代码库变得更难理解和测试。即使是AI智能体也会受到这种债务的影响,因为它们需要花时间解析死代码,导致会话更长、成本更高。

移除过期标志看起来像是一个简单的搜索-删除问题,但一次清理可能会涉及数十个文件,涵盖布尔逻辑、辅助函数、调用点、配置和测试。有些测试可以删除,而另一些则需要围绕新的默认行为重写。要安全、自动地完成这项工作,需要代码理解、判断力,以及跨系统的仔细验证。

在这篇博客中,我们分享如何构建了一个AI系统,能够大规模自主删除过时的生产代码,以及将智能体工作流引入软件开发生命周期所获得的经验。

背景

功能开关清理在功能推出后往往被降级处理:团队继续开发下一个功能,移除旧标志的工作与新产品功能开发竞争优先级。在Uber的规模下,这种权衡会迅速累积。标志在众多团队和仓库中持续创建和推出,小的清理延迟变成了庞大的积压。

这正是Uber开发者平台发挥作用的地方。我们将AI在代码方面的专业知识应用于将过期标志清理转化为可规模化、自动化的的工作流,并在流程中内建了安全检查、验证和审查点。

为什么清理比看起来更难

基本思路似乎很直接:判断某个标志是否过期,找到它的使用位置,然后内联其永久值。但即便是一个简单的案例也会迅速展开成复杂问题。考虑图1中的示例。

在图1中,new_order_pipeline 标志控制请求是走新管线还是遗留管线,标志值从配置传入处理函数。

假设 cfg.UseNewPipeline 已经完全推出,且始终为 true。将第3行的布尔调用内联为 true 是直接的。但接下来是一连串的传播决策:

- processOrder 函数在第9行接受 useNewPipeline 作为参数。在这个例子中,唯一显示的调用方在第20行传入 cfg.UseNewPipeline。如果所有调用方现在都传入已推出的值,那么这个参数就可以移除:它存在的唯一目的就是携带该标志。但在真实代码中,布尔参数可能有多个调用方或来源,因此清理工具必须首先检查所有调用点。
- 值被传播到 processOrder 后,第13行的遗留分支变成不可达的,可以删除。但单独看第9-14行,两个分支看起来仍然可能被走到,因为函数仍然接受一个布尔参数。
- 第3行的配置字段 UseNewPipeline 也可能可以移除,但前提是没有其他代码读取它或依赖配置结构体的形状。

在这个例子中,标志值从配置初始化开始,通过结构体字段、调用点和函数参数到达死代码位置。内联常量是机械性的操作,但决定下一步移除什么需要上下文:谁调用这个函数、测试覆盖了什么,以及简化结构体或签名是否安全。静态工具擅长处理确定性的重写;AI在传播决策方面最有价值。

架构(AutoClean:系统)

为了自动化功能开关清理,我们构建了 AutoClean,这是一个连接到Uber功能开关管理系统、检测过期标志并将其转化为干净、经过测试的 diff(变更集),供人工审查的系统。AutoClean 在Uber的 monorepo(单仓库)中持续运行,端到端地处理清理生命周期:识别安全移除的标志、重写代码、修复CI失败、验证最终 diff,并将其路由给工程师审查。在这篇博客中,我们重点介绍核心转换和验证引擎,这是最有趣的智能体推理发生的地方。

第1阶段:智能体和 PolyglotPiranha 重写代码

我们使用一个智能体自主处理清理工作。该智能体可以访问完整代码库和一组限定的工具:文件搜索、代码编辑,以及一个构建在 PolyglotPiranha 之上的布尔代数和条件简化工具(PolyglotPiranha 是我们的轻量级代码转换引擎)。

使用 PolyglotPiranha 是一个重要的设计选择。AutoClean 是一个智能体系统,但这并不意味着智能体应该处理每一步。我们希望尽可能在流程中保持确定性,特别是对于那些一旦出错就可能静默改变程序行为的转换。布尔代数非常适合确定性处理,因为在简化条件时一个小错误就可能改变生产环境中执行的路径。我们不是让智能体自行执行这些重写,而是通过 MCP®(模型上下文协议)让它访问 PolyglotPiranha。

回到我们的例子,智能体执行以下操作:

1. 找到 flipr.GetBool("new_order_pipeline“, false) 调用并将其替换为 true。
2. 调用 PolyglotPiranha 简化生成的表达式和条件语句。
3. 将值追踪到 processOrder,发现 useNewPipeline 始终为 true,移除该参数,并删除死掉的 else 分支。
4. 继续迭代,直到没有进一步的清理项。

第2阶段:修复CI中的失败

智能体完成重写后,AutoClean 会生成一个 diff 并让CI运行。智能体可以在工作时本地运行测试,它们也确实这么做了。但一次标志清理可能会触达 monorepo 中扇出到各处的代码。重要的不仅仅是附近的测试是否通过,而是改动是否仍然能在各处构建并全部通过。CI 是这方面的真相来源。

我们的CI在众多目标上运行大量作业,并会暴露重写循环在本地可能看不到的问题。如果CI失败,AutoClean 会读取每个CI失败并将其路由到修复器。成本低的确定性修复器首先运行,它们处理机械性案例,比如移除现在不再使用的默认值或清除 lint 错误,并将任何无法识别的情况传递下去。更难的案例(比如决定测试应该删除还是重写)则交给智能体修复器处理。每次修复后,我们推送更新,CI再次运行,循环持续直到 diff 变绿或尝试次数用完。

我们严格限定每个修复器的职责范围,让它只能操作它应该操作的内容。例如,测试修复器只能编辑测试文件。如果没有这个限制,智能体有时会通过修改底层生产代码来让失败的测试通过,而这正是我们试图防止的结果。

第3阶段:验证结果

CI变绿后,AutoClean 产生一个能构建且通过测试的 diff。但这并不总是意味着清理可以安全地交给人工审查员。

一个过期标志清理可能通过CI,但仍然在更微妙的方式上出错。例如,智能体可能通过删除一个测试来修复问题,尽管该测试覆盖了应该保留的行为。它也可能因过早停止而产生不完整的清理,比如内联了标志的已推出值但没有将简化完全传播到周围的代码中。这两种情况都不一定会被CI检查捕获。

因此,在将 diff 送交审查之前,AutoClean 会运行一个额外的验证步骤。第一层是静态检查。这些检查捕获我们可以直接编码的机械性清理问题,比如一个平凡布尔函数现在总是返回 true、一个本应被简化的条件,或者仍然只能通过过期标志路径到达的死代码。

静态检查通过后,diff 进入我们的智能体验证层,称为 CodeCourt。CodeCourt 处理那些难以编码为确定性规则的案例。

如果一个 diff 通过了这些层,它就通过了我们的自动化正确性检查,可以进入人工审查阶段。人工审查员可以专注于业务层面的判断,比如”我们现在应该移除这个标志吗?“而不是扫描机械正确性问题,比如”清理是否意外改变了行为?"

CodeCourt 将我们从清理智能体观察到的失败模式转化为明确的正确性属性。这些属性是对清理智能体自身改进的补充。清理智能体正在解决一个长的、多步骤的任务,上下文中包含许多指令:移除过期标志、传播简化结果等等。随着指令集的增长,可靠的指令遵循变得更加困难。一些约束被埋没,一些在实践中冲突,智能体可能会优化最直接的信号(如通过CI),而违反我们关心的清理要求。

CodeCourt 将这些要求从主清理循环中分离出来。我们不是依赖一个智能体在编辑代码时记住并满足所有约束,而是定义最终 diff 必须满足的明确属性。每个属性描述清理 diff 在交付前必须做到的一件事。例如:

- 无新增:清理应只移除或简化代码,绝不引入新代码
- 保持语义:程序的可观察行为在清理后必须保持一致。
- 无意图漂移:清理应聚焦于所请求的标志。即使其他标志相关,也不应移除它们
- 无智能体痕迹:清理不应留下来自智能体清理推理的注释或痕迹

每个属性都有自己的审查智能体,其提示词完全专注于检查这一件事。所有属性智能体并行运行,它们的判定汇总为一个简单决策:

- 全部通过:diff 进入人工审查
- 任何一个失败:diff 在人工看到之前就被放弃

这种分解是有意为之。早期,我们尝试过让一个智能体端到端地审查每个 diff,但发现使用专注的审查子智能体效果更好。

结论

功能开关清理很容易被推迟,因为每个标志单独看起来都很小。但在Uber的规模下,这些小的遗留物会迅速累积:过期分支、未使用的参数、过时的测试,以及工程师和智能体仍然需要阅读和推理的额外代码路径。

AutoClean 让这种清理变成持续性的工作。它结合了智能体、确定性工具、CI和验证检查,使每个 diff 在到达人工之前就准备好接受审查。审查者随后可以专注于最终决策:该标志是否真正准备好移除。

在过去几年中,AutoClean 已经清理了超过 16,000 个标志,清理了超过 1,000,000 行代码。随着新标志继续在Uber的代码库中引入,AutoClean 与之同步推进清理流程,使过期标志债务不会随时间悄然累积。

致谢

我们感谢移动实验团队、功能开关管理团队,以及众多Uber产品团队的支持,使这项工作成为可能。我们也衷心感谢多年来维护 AutoClean 的同事,包括 Ameya Ketkar 和 Stefan Heule。

封面图片来源:由 Gemini 生成
Model Context Protocol 是 LF Projects, LLC 的注册商标。

作者:Daniel Ramos, Diego Marcilio, Nikhil Parasaram, and Yuxin Wang
https://www.uber.com/us/en/blog/ai-code-cleanup/

阅读原文
📚 相关主题 工程实践技术债

订阅 AI Pulse

每天 08:00 · 12:30 · 18:30 · 23:50 更新