[控场AI]
· 8 分钟阅读· 4,207 字

AI代码审查的盲区:diff之外的回归与决策漂移

AI代码审查的盲区:diff之外的回归与决策漂移

主流AI代码审查只读diff,会系统性漏掉跨文件回归与团队决策漂移两类隐性Bug。

本文指出当前AI代码审查工具存在两个结构性盲区:其一是"diff之外的回归"——被改函数的调用方散落在diff范围之外,审查器从不读取;其二是"决策漂移"——新代码违反了团队历史上达成的专属约定,但通用审查器无从感知。作者通过实测揭示,现成代码图谱工具召回率仅57.4%,且习惯将空结果误报为"安全",这是回归被错误放行的根本原因。对此,文章提出两项解法:一是构建带准确率自知的代码图谱,将不确定性显式传递给审查模型;二是建立集中化的决策存储,每条决策附带作用范围与来源链接并经人工确认。两类检查应与现有diff级审查并行运行,而非相互替代。

大多数AI代码审查工具的工作方式如出一辙:读取Pull Request的diff,顺带看看周边的几个文件,然后对所见内容发表评论——缺失的空值检查、未处理的Promise、手工拼接的SQL字符串。它们在这方面已经做得相当不错,如果你的团队正在使用这类工具,完全可以继续用下去。

但有一整类bug对这种审查方式是隐形的。diff看起来没问题,问题却藏在审查器从不查看的地方。原文把这类问题归为两种:diff之外的回归(regressions outside the diff)和决策漂移(decision drift)。

AI代码审查的盲区

第一类盲区:diff之外的回归

设想这样一个场景:某个函数新增了一个必填参数。提交者在PR里更新了这个函数,以及他记得的两个调用方。但实际上还有三个调用方散落在其他包里。它们不在diff范围内,审查器永远读不到;如果其中一个调用方经过了any类型的适配器,类型检查器也帮不上忙;而只要没人测试那条路径,测试就能照常通过。

结论很讽刺:diff本身是正确的,但这次变更是错的。

随着编程智能体(coding agent)的普及,这个问题会变得更糟。原文指出,智能体在修改一个函数时,除非你明确要求,否则很少会去主动寻找每一个调用方,而它生成的PR通常整洁、自信、滴水不漏。只读这个PR的审查器根本挑不出毛病。

要捕捉这类问题,审查器必须在读diff之前先回答一个问题:谁依赖了被改动的代码?

编程智能体(coding agent)是指能够自主完成代码编写、修改和提交等任务的AI系统,例如GitHub Copilot Workspace、Claude Code、Cursor Agent等。与传统代码补全不同,这类智能体可以接受高层次的自然语言指令,自行规划并执行多步骤的代码变更,最终生成一个完整的PR。它们的输出通常格式规范、逻辑自洽,但由于工作方式是"按需查找"而非"全局扫描",容易在不被明确要求的情况下忽略对其他代码模块的影响评估。随着这类工具在工程团队中的普及,人工审查者对智能体生成代码的信任度往往高于对人类同事代码的信任度,这反而加大了此类隐性回归被漏过的风险。

第二类盲区:决策漂移

第二类问题更安静。假设你的团队几个月前有意识地做了一个决定:所有LLM调用都必须经过一个共享的客户端,这样成本追踪和重试逻辑就能集中在一处。这个决定记录在一个已合并的PR里、一条评审讨论串里,以及CLAUDE.md的某一行中。

现在,一个新的PR从一个新文件里直接调用了provider的SDK。它能跑通,也有测试。但它恰恰是你们当初决定不要去做的那件事的开端,而diff里没有任何东西能提示这一点。

通用型审查器无法标记这类问题,因为这条规则不是通用规则——它是你们团队专属的,存在于你们的历史记录中,而不在被改动的代码里。要捕捉它,审查器需要回答第二个问题:我们的哪些决策适用于被改动的文件?

回答“谁依赖了这段代码”比想象中更难

解决第一个问题最直观的工具是代码图谱(code graph):索引整个仓库,然后查询每个被改动符号的调用方。原文作者的回归检查正是基于此构建的,但他们做了一件关键的事——实际测量了它的准确率,因为漏掉一个调用方就意味着漏掉一次回归。

他们用TypeScript LanguageService标注了真实的调用点作为基准(ground truth),然后拿现成的代码图谱工具去对比。在自家的TypeScript monorepo上,一款开箱即用的图谱工具表现如下:

  • 召回率仅57.4%:47个真实调用方只找到了27个;
  • 26个函数中有17个返回“零调用方”,尽管每一个都确实有调用方;
  • 部分遗漏甚至是同文件内的调用,而不只是跨包调用;
  • 每一个空结果都被标记为“精确”,风险等级LOW。

最后一点才是真正要命的。一个会说“我不知道”的工具是可以接受的;但一个在三个地方都调用了某函数时却声称“没人调用这个,可以安全修改”的工具,正是回归被审批通过的罪魁祸首。

作者还在开源项目Vite上用另一个引擎做了同类研究。该引擎有67.9%的概率能准确命名调用方,而且全部185个调用点都落在它报告的某个调用方之内——它只是把包裹调用的外层函数认错了,而非漏掉调用。这是一种“好得多的失败”,因为审查者顺着报告的调用方读下去,仍然能找到那个调用。

作者从中总结出三条经验:

  1. 空的调用方列表不等于安全。 应把它当作“未知”,无论如何都去搜索代码,在判定变更无影响之前先读一读可能的消费方。
  2. 用真实的oracle在真实仓库上测量你的图谱。 一个干净的小型测试用例会读出100%的准确率,却什么也说明不了——遗漏只在规模上才会暴露。
  3. 把不确定性传递给审查器,而不只是答案。 他们的影响分析结果带有明确的“approximate”标签,并附上可能不完整的原因列表(动态分派、re-export、反射调用),审查模型能看到这些。

回答“哪些决策适用”需要一个决策存储

团队的决策散落各处:已合并的PR、评审讨论串(某人说“请别这么做,我们用X”)、ADR文档、README、CLAUDE.md。要拿一个PR去对照它们,你需要把它们集中到一处,每条决策都带上作用范围(它覆盖的路径)和来源链接。

作者的做法是从历史记录中自动挖掘候选决策,但挖掘出的决策只是猜测。因此它们先以“提案”形式存在,由团队成员逐条批准、编辑或拒绝。引用了已批准决策的发现可以阻止合并;引用未确认提案的发现则被标注出来,权重更低。

指回来源的那条链接比预想的更重要。“这违反了一个决策”很容易被打发掉;而“这违反了六个月前那个PR里的决策,讨论串在这里”则很难被忽视。

把噪音压下去

这两类检查都极易变得嘈杂。一个对每个被改函数的每个调用方都报警的审查器,一周内就会沦为摆设。作者分享了有效的做法:

  • 两段式审查。 先用一个便宜的模型对候选发现做初筛,再由一个更强的模型对照实际代码逐条确认,然后才发布。
  • 每条发现都要有证据。 要么点名受影响的调用方(名称与文件),要么给出决策及其来源。没有证据,就不发评论。
  • 反馈要能留存。 被团队忽略的发现,在下一次推送时不会再次出现。

与现有审查器并行,而非取而代之

原文的核心主张清晰且务实:diff级审查和上下文级审查捕捉的是不同的bug,所以应该两者并用。diff审查器抓住空值检查,上下文检查则抓住另一个包里的调用方和六个月前的那条决策。

作者将这套能力做进了名为Tently的GitHub PR审查工具中,它会对照依赖代码和团队决策检查每一处变更。同一个决策存储还通过一个开源的MCP server(tently-team/tently-mcp)在智能体写代码时就可用,让Claude Code、Cursor或Codex能在PR存在之前就查到规则。

无论你用什么工具,对任何PR——无论人写的还是智能体写的——这两个问题都值得一问:

  1. 谁依赖了被改动的代码?
  2. 我们的哪些决策适用于被改动的文件?

正如作者所言:如果你的审查器回答不了它们,那么就有团队成员正在凭记忆回答它们。而记忆,是最不可靠的审查基准。

MCP(Model Context Protocol)是Anthropic提出的一种开放协议,旨在标准化AI模型与外部工具、数据源之间的交互方式。通过MCP server,开发者可以将代码库信息、文档、API等上下文以结构化方式提供给支持该协议的AI客户端(如Claude Code、Cursor等),使模型在生成代码时能够主动查询项目特定的规则和约束,而无需将所有上下文塞入prompt。文中提到的tently-mcp即是一个将团队决策存储暴露为MCP工具的服务端实现,让智能体在编写代码阶段就能感知团队规范,将合规检查前移到PR生成之前,而非事后补救。这种"左移"(shift left)的思路与软件工程中将测试和安全检查提前到开发早期的趋势一脉相承。

背景补充

代码图谱(code graph)是对代码库中符号之间依赖关系的静态索引,通常记录函数调用、模块导入、类继承等关系,以有向图的形式存储。常见的构建方式包括:基于语言服务器协议(LSP)的语义分析、基于AST(抽象语法树)解析的静态分析,以及专用工具如Sourcekit、tree-sitter等。其准确率受多种因素影响:动态分派(运行时才能确定实际调用目标)、通过index.ts等文件进行的re-export(导出链路被截断)、反射调用(如obj[methodName]())等情况均会导致静态分析工具无法追踪到真实调用方。文中提到的TypeScript LanguageService是微软官方提供的TypeScript语言服务API,能够进行语义级别的"查找所有引用"操作,精度高于纯文本或AST级别的工具,因此被用作衡量其他工具准确率的基准(ground truth)。

ADR(Architecture Decision Record,架构决策记录)是一种轻量级文档实践,用于记录团队在架构或技术选型层面做出的重要决策,包括决策背景、考量选项、最终选择及其理由。ADR通常以Markdown文件形式存储在代码库中(如docs/adr/目录),每条记录对应一个独立决策,状态可标记为"提议中""已接受""已废弃"等。与README或注释不同,ADR强调保留决策的历史语境,让后来的团队成员能够理解"为什么这样做"而不仅仅是"做了什么"。文中提到的CLAUDE.md则是专门面向AI编程助手的项目约定文件,用于向Claude等模型说明项目的编码规范、禁止行为和团队惯例,是当前AI辅助开发实践中新兴的一种"机器可读的团队规范"载体。

#AI代码审查#代码回归#决策漂移#代码图谱#Pull Request审查#编程智能体#code review
分享:

相关推荐