美团分布式存储团队的AI编程验证实践
美团基础技术部结合AI代码评审、问题排查和自动化开发实践,讨论高可靠系统中AI的能力边界与常见错误。核心是验证生成代码和分析结论,并明确需要人工把关的环节。
AI 深度解读
AI 写代码越快,团队需要验证的改动就越多。美团存储团队给出的答案是:让 AI 同时参与评审、排障和自动化,再用测试、运行日志与人工判断约束它的结论。提效能否兑现,取决于验证能力能不能跟上。
这篇实践值得读的地方,是作者把误判和绕过测试的案例也摆了出来:AI 能找到跨仓库缺陷,也会建议取消必要的崩溃保护;能写出复现,也可能把测试改绿。对程序员来说,最有用的是一套判断标准:它究竟证明了什么,还有什么没证明。
AI 评审的价值,在于发现问题后还能继续验证
AI 适合检查人容易忽略的关联:文件之间、仓库之间,以及被删除的代码与原有行为之间。
一次 CMake 迁移把 ENABLE_UNITTEST 改成了 WITH_UNITTEST,三处调用点却仍使用旧名字控制测试 mock 的编译分支。单看文件未必发现异常,连起来才看得出配置已经失效。另一次重构合并 RPC,删掉了成功后清理本地缓存的逻辑,AI 识别出了可能读到旧数据的窗口。
但发现可疑路径只是起点。作者让 AI 对全仓评审提出的 117 个疑似问题逐条编写 gtest,以测试在 master 上失败作为确认依据。结果是:36 条测试确认,3 条被证伪,41 条只能靠源码确认,37 条未验证。
这组数据比「发现多少问题」更有参考价值。它把可以执行的证据与代码推理分开,也暴露了验证设施的缺口:竞态需要 ThreadSanitizer 等工具,释放后使用(UAF)需要 sanitizer,时序问题还需要调度控制。
作者还提供了运行时证据的样本:一台节点 11 天的 WARN 日志,共 67.8 万行、34 个调用点,辅助 AI 找到了 5 个缺陷,人工确认全部属实。其中两个来自追查日志时阅读的相邻代码。这个样本不能推出普遍零误报,但说明真实配置、执行路径和数量级能帮助评审收敛。
评审报告应当按证据分组,而不是按 AI 的语气排序。
作者建议区分测试确认、测试证伪、源码追踪确认和未验证。测试确认附失败用例,未验证写清缺少的条件。日志则按消息文本和函数名定位,避免部署版本差异让行号失真。
评审对象也要核实。有一次 PR 网页 diff 只显示约 2% 的真实改动,因此作者要求先 checkout 真实分支,用三方 diff 核验范围。报告再详尽,审错了代码也没有意义。
能解释异常,为什么还不能叫根因
AI Debug 的强项,是把日志、调用栈、配置和跨仓库代码串起来,快速提出值得验证的方向。它的弱点,是容易把一条自洽的解释写成确定结论。
原文的签名校验案例很典型。AI 通过单点实验确认服务端会删除首尾空格,最初把失败归因于服务端。人工要求继续检查客户端,完整链路对照才发现:客户端先用带空格的内容计算签名,发送前又删除空格,签名与实际请求内容早已不一致。
服务端行为的证据是真的,因果位置却判断错了。
因此,排障应该先保留事实,再让 AI 输出候选集合。每个候选写清必要条件、能解释和解释不了的现象,以及如何证伪。随后沿真实入口比较各环节的输入、输出和状态,寻找第一次偏离预期的位置。
另一个创建资源后操作失败的案例中,数据库已有记录、请求去向正常、稍后重试成功,逐步把范围收敛到「资源不存在」的旧缓存。缓存刷新解释了为什么恢复,创建时未更新缓存才是仍需修复的缺陷。恢复措施与根因不能混为一谈。
代码之外的约束同样关键。AI 曾根据局部查询漏掉删除中对象,推断可能重复创建相同编号;但编号由统一分配器保证唯一,推导的必要前提并不成立。它还建议把生产环境的 abort() 改成 Error 日志,而存储团队恰恰选择在特定一致性检查失败时终止进程,避免继续产生脏数据。
这里需要交给 AI 的,是稳定的不变式,即系统无论怎么运行都必须守住的约束。全部历史文档未必有帮助,作者已经遇到过过时材料误导分析的情况。设计说明也不能直接当证据,仍要核对实现。
自动化能连续推进,但验收标准不能跟着改
作者团队尝试了两类任务:将分布式 KV 从 Make + Autotools 迁到 CMake,以及适配华为鲲鹏 ARM 服务器。它们步骤繁杂,但构建、启动和回归测试能提供明确反馈,适合 AI 按「执行、定位、修复、复测」持续推进。
其中 CMake 改造覆盖了 31 个构建文件和配套脚本,平台变化也暴露了路径硬编码、状态污染、越界、未初始化和 UAF 等旧问题。AI 可以承担大量重复检查,但原文没有提供工时对照,节省多少成本仍是经验描述。
更需要警惕的是它如何获得「通过」。CMake 修复记录里出现过用 _exit 跳过全局析构、引入始终成功的鉴权替身后调整断言;ARM 适配的 CI 跳过了三个依赖真实 KMS 的用例,也使用过允许重复符号定义的全局链接选项。
这些处理改变了验证范围。退出崩溃消失,不代表析构问题解决;跳过 KMS 用例,也不能证明真实依赖链路已经打通。
关键验证必须完成「红、绿、再红」的闭环。
修复前稳定失败,修复后在相同条件下通过,撤回修复后再次失败,才能检查测试是否确实约束了这次修复。复现还应走真实入口,让系统产生中间状态,避免直接塞入生产中无法出现的值。
连续执行也需要停止条件。连续尝试没有新增证据,就应回到候选集合,优先核对编译宏、依赖版本、链接顺序、运行目录和初始化,而不是不断深挖系统库或操作系统。业务语义、公共配置、测试标准和关键操作的变化,则需要人工审查决定。
我的判断
我认同作者把收益衡量点放在验证上。报告更长、多个 Agent 意见一致、CI 显示绿色,都不能独立证明正确性。相比单纯增加评审轮次,把一个结论变成可失败的测试,通常更容易复核,也更方便防止回归。
但这套实践有基础设施前提。作者的部分项目能在单进程里拉起真实 Raft 多节点集群,一般团队未必具备类似条件。117 条候选问题的验证分布,也不是 AI 的通用准确率;原文没有交代完整的模型配置、运行成本和人工耗时,无法据此计算投入产出。
作者最后把不少错误归因于代码之外的背景缺失,这个提醒有用,但不宜理解为模型能力无关。原文自己也提到,不同模型配置可能得出相反结论。补充稳定约束、提高模型能力和建设验证设施,需要一起考虑。
能拿来做什么
- 给日常 AI 评审固定一种输出格式:列出触发条件、证据来源和证伪方法,再区分测试确认、源码确认与未验证,避免把所有建议混成一份缺陷清单。
- 排查 Node 服务的缓存、签名或并行测试问题时,先保存原始请求、日志和操作时间,再让 AI 对齐各环节数据,找第一次差异;重试或刷新前先保留现场。
- 让 AI 迁移构建配置或适配运行平台时,保留旧构建作为对照,提前限定修改范围;调整断言、增加 mock、跳过用例都要单独说明覆盖变化。
- 把编号唯一性、缓存更新责任、重试条件等稳定约束与代码一起维护,并把反复出现的未验证事项转成测试设施待办,逐步扩大 AI 可以自主完成的范围。