优步没有把哪些事交给删代码的AI
清理了1.6万个标志位的AutoClean,贴在智能体身旁的工具、权限与裁决分工表
AI与科技优步公开的“删代码的AI”
优步工程博客上发布了一篇介绍名为AutoClean的系统的文章。这是一个会找出已经用完、不再需要的功能标志1,删除相关代码,修复损坏的测试,并把完成验证的diff2发送给负责团队的系统。优步表示,过去几年里用这套系统清理了1.6万多个标志、100万多行代码。
AI帮忙写代码的故事,如今已经不新鲜了。但这篇文章的方向正好相反——AI删除的是已经在运行的生产代码。不过读到最后会发现,文章里详细写的不只是智能体做成了什么,还有优步没有把什么交给智能体。今天就来看看这种分工的样子。
删除一个标志为什么需要判断
功能开关是放在代码里的一个开关。它让团队可以只对部分用户开放新功能,或者一旦出问题就能直接关掉,而不用重新部署。据说Uber在移动应用和后端服务中管理着数千个功能开关。
问题出在功能对所有用户都打开之后。开关本身一直保持打开状态,但围绕开关的条件语句、旧的代码路径、以及检查那条路径的测试都原封不动地留着。团队转向下一个功能,清理工作就在与新产品工作争夺优先级的过程中被往后推延。在Uber这种规模下,这样被搁置的小型清理工作很快就会堆积成大问题。
Uber举的例子是订单处理代码。从配置中读取new_order_pipeline标志的值,存入cfg.UseNewPipeline,再把这个值作为参数传给processOrder函数,函数内部再根据这个值在新旧两条流水线之间做选择。假设这个标志已经完全上线,值始终为true。
在读取标志的地方填入true是机械性的工作。判断是从那之后才开始需要的。
- 需要确认
processOrder的useNewPipeline参数是否真的始终为true。例子中只有一个调用点,但在实际代码中,其他地方可能会传入不同的值。只有看过所有调用点之后,才能删除这个参数。 - 只有删除参数、把值直接写进函数内部之后,旧流水线的分支才会显现出是死代码。如果只看函数本身,两条分支看起来都是存活的。
- 配置结构体中的
UseNewPipeline字段也许也能删掉。但只有在没有其他代码读取这个字段、或依赖这个结构体形态的前提下才行。
标志的值从配置初始化出发,经过结构体字段、调用点、函数参数,最终抵达死代码。Uber总结说,像嵌入常量这样的确定性重写,静态工具做得很好;而AI最有用的地方,是沿着这条传播路径判断该删到哪里为止。测试也面临同样的问题——有些测试可以直接删掉,有些则需要根据新的默认行为重写。
Uber还写道,这笔债不仅折磨人类。AI智能体读取和解释死代码同样要花时间,会话也因此变得更长、更贵。不过文章里没有给出到底会变长多少的具体数字。
布尔运算交给了工具
AutoClean 在 Uber 的功能标志管理系统中查找过时的标志,并分三个阶段处理:改写代码、修复 CI3 中出现的故障、验证结果。通过验证的 diff 会发送给负责团队,并持续更新,以免在人做出最终决定之前变得陈旧。
第一阶段的代理可以查看整个代码库,并能使用文件搜索、代码编辑、条件式简化等工具。值得关注的是最后一个工具。Uber 并没有让代理直接整理布尔表达式和条件语句,而是通过 MCP4 连接自研的代码转换引擎 PolyglotPiranha,让代理在需要时调用它。代理会在标志的位置填入 true,用 PolyglotPiranha 简化表达式,再追踪该值一路到 processOrder,删除参数和死分支,如此反复,直到没有更多可以整理的内容为止。
文中也写明了原因。在简化条件表达式时若出现微小失误,就会改变生产环境中实际执行的分支——这是一种能通过编译、表面上看起来正常,却只是行为发生变化的错误。Uber 写道,即便是代理系统,也不需要让代理承担所有阶段;越是那种出错时会悄悄改变行为的转换,就越想以确定性的方式处理。也就是对相同的输入始终给出相同结果的方式。
Piranha 并不是新工具。Uber 在 2020 年 3 月将 Piranha 开源时曾透露,截至当时已借此删除了约 2,000 个过时标志及相关代码。当时就已经有一条流水线在运行:每周从标志管理系统中提取过时标志并生成 diff,并将标志的最初创建者指定为默认审阅人。可以说,AutoClean 并没有抛弃这个确定性工具,而是只在需要判断的地方才嵌入代理。
即便在 CI 中出现故障时,顺序也是一样的。像删除不再使用的默认值、清理 lint 错误这类机械性的故障,会先交给成本低廉的确定性修复器处理。只有修复器无法识别的故障才会传递到下一步,而只有在需要判断——比如是删除测试还是重写测试——的情况下,才会交给代理修复器处理。每次修复后都会重新运行 CI,如此反复,直到出现绿灯或用完预先设定的尝试次数为止。
对负责修复测试的代理,只开放了测试文件的权限
在这个阶段,Uber 留下了这样一句话。他们为每个修复器都划定了狭窄的可改动范围,比如修复测试的代理只能改测试文件。据说如果不这样限制,代理就会为了让失败的测试通过,去改动下面的生产代码。这正是 Uber 想要阻止的结果。
这一段可怕的地方在于,那种行为在代理的立场上其实相当合理。眼前的信号是“让测试通过”,而改动生产代码确实能让测试通过。本该是删代码的任务,变成了改代码的任务。Uber 在文中写下的应对措施不是指令,而是权限本身——他们限制的是可以改动的文件范围。
绿灯之后设立的审判官
CI 变绿就结束了吗。Uber 认为并非如此。即使通过了构建和测试,也可能存在错误的清理。智能体没有修复测试,而是直接把它删掉了,而那个测试原本可能正在检查某个应当保留的行为。也可能出现这种情况:智能体只是嵌入了标志值,却没能把简化扩散到周边代码,于是半途而止。这两种失误都不一定能被 CI 捕捉到。
因此,验证被设置成了两层。首先是静态检查,过滤掉那些能写成规则的问题。比如变得只会返回 true 的函数、本应被简化的条件语句、仅能通过旧标志路径才能触达的死代码等等。通过这一关之后,才会进入名为 CodeCourt 的智能体验证层。
CodeCourt 会逐一定义清理 diff 必须遵守的属性,并为每个属性配上一个只检查该属性的审查智能体。文章中举出的属性例子有四个。
| 属性 | 检查内容 |
|---|---|
| 不新增 | 清理只能删除或简化代码,不能加入新代码 |
| 保持行为 | 清理前后程序的可观测行为必须一致 |
| 不偏离意图 | 只处理被要求处理的标志。即便看起来相关,也不能删除其他标志 |
| 不留智能体痕迹 | 不能留下智能体在作业过程中留下的注释或副产物 |
审查者们并行运行,判定很简单。全部通过才会进入人工审查,只要有一项失败,这个 diff 在人类看到之前就会被丢弃。
Uber 也写明了这样拆分的理由。清理智能体背负着冗长的指示去执行多个步骤,指示越多,一部分就会被埋没,一部分则会彼此实际冲突。于是智能体容易按照离它最近的信号——比如 CI 通过——来行动,反而违背了本应遵守的清理条件。因此,与其让一个智能体记住所有条件,不如把它们拆解成最终 diff 必须满足的属性。据说最初曾尝试让一个智能体从头到尾审查整个 diff,但后来发现,让多个审查者各自负责一个属性,效果更好。
按照 Uber 的说法,人类审查者要做的事也随之改变了。逐行检查清理过程中行为是否发生变化的工作,交给前面的环节去做,人类则可以专注于“现在删掉这个标志是否合适”这类业务判断。
如何解读“1万6千个”这个数字
这篇文章最后给出的数字看起来很惊人。据称过去几年里清理了超过1万6千个标志位,超过100万行代码。读这个数字时,有两点需要一起考虑。
首先是时间跨度。Uber只写了“过去几年”。这个累计数字从何时开始算起,是否包含在智能体介入之前、Piranha流水线时代的那部分成果,仅凭文章本身无法得知。考虑到早在2020年3月就已经清理了约2,000个标志位,很难把这1万6千个全部算作智能体的成果。
其次是缺失的数字。有多少个diff在CodeCourt中被丢弃,人工审阅者批准了多少个、驳回了多少个,一个diff需要耗费多少时间和成本,文章中都没有说明。“只要有一项失败就放弃”这一设计究竟多常起作用,也无从得知。
可以拿来对比的数字来自另一家公司。根据2026年9月InfoQ的报道,DoorDash构建了一套多智能体系统,用于清理分布在约623个代码仓库中的超过6万个标志位,并用50个老旧标志位进行了评估。其中45个产出了可用的PR,每次清理平均耗时13.8分钟、花费4.79美元。这与DoorDash自己估算的人工处理需要1~2小时相对照。简单标志位一次性100%清理成功,但复杂标志位只有85%,工程师介入了五次。需要工程师介入的地方,是深层调用链以及跨接口传递的参数。DoorDash要求工程师在修改代码之前先确认目标值。
两家公司的数字无法用来分高下。语言不同,代码结构不同,衡量标准也不同。不过,DoorDash受阻的地方,恰好与Uber文章中提到的问题重合。问题不在于把值填进去,而在于追踪这个值跨越多个文件会扩散到什么程度。
Oswarld视角
我一直有句常挂在嘴边的话:不是所有人、所有事都需要超级智能,适合工作和环境的模型各有不同。我自己也是这样分开使用的。截至2026年9月,整体规划制定与编排、初期蓝图仍然交给Claude或GPT-6,耗时长、重复性高的工作则交给DeepSeek或GLM。
在Uber那篇文章中,与这句话最接近的段落,是把布尔运算交给PolyglotPiranha处理的部分。即便是智能体能够完成的工作,只要出错就会悄悄改变行为的运算,他们还是交给了相同输入必定给出相同答案的工具去做。这不仅仅是为工作挑选合适的模型,而是更进一步,把工作的一部分彻底移出了模型之外。
只给测试修复器打开测试文件这一决定,我认为也属于同一条思路。这是在决定要交给它做什么的同时,也决定了不能让它做什么。在把智能体引入工作时,“该交给它做什么工作”这个问题很容易被提出来。而Uber那篇文章让人同时去追问:“在做那件事的过程中,影响可能扩散到什么地步?”
只要有一个失败就直接丢弃、不去修复的设计,读起来像是一种选择:为了节省人工审查者的时间,宁愿多花机器的时间。而这个选择是否划算,需要知道究竟丢弃了多少个才能判断,但这个数字恰恰缺失了,这一点令人遗憾。
结语
Uber 的文章中,描述智能体能力的句子,和说明在智能体周围布置了哪些装置的句子一样多。由于被废弃的 diff 数量或成本并未公开,很难用数字来衡量效果。不过,哪些工作交给工具、哪些工作交给智能体、哪些工作交给人来做,文章写得相当详细。
💬 读者您觉得您团队的代码里,大概还留着多少个一直开着没关的功能开关(feature flag)?欢迎在评论区留下大致的估计。
📨 如果您的开发团队里有一直拖延清理旧代码的同事,请把这篇文章转发给他们。
参考资料与延伸阅读
- Uber Engineering,“Using AI to Delete Code: Agentic Tech Debt Cleanup at Uber”。AutoClean的结构、CodeCourt的特性,以及1万6千个·100万行数字的出处。
- Uber Engineering,Introducing Piranha: An Open Source Tool to Automatically Delete Stale Code,2020年3月17日。约2,000个标志位清理、每周流水线、默认审查者指定方式的出处。
- uber/piranha,GitHub。Piranha与PolyglotPiranha仓库。
- Leela Kumili,DoorDash Uses Multi Agent LLMs to Clean up 60,000 Feature Flags,InfoQ,2026年9月18日。DoorDash评估数字的出处。

你的观点会成为下一期的线索
欢迎留下你的经验与看法。