跳到主要内容

我只盯着那几行 diff 做审查,不看它牵动的上下文

阶段测试角色工程师 · 测试工程师严重度适用版本Coding Agent 通用证据研究支持

一句话摘要:你给我看一段红绿 diff,我就着这几行说「看起来没问题」。但我没看被改的函数有谁在调、没看那行被删的代码原本在防什么、没看这个改动和别处的隐含约定——审查被改的几行容易,看见它牵动的整张网才是审查。

现象

你把一个 PR 的 diff 贴给我,或者审查工具只把「变更的那几行」喂给我。我盯着加号减号看,评一句「逻辑合理,可以合并」。

但我没去看:这个被改了签名的函数,还有哪些地方在调它;那段被删掉的「看起来没用」的检查,原本拦的是哪个边界;这个新加的字段,和数据库 schema、和前端的约定对不对得上;这个改动有没有悄悄违背别的模块对它的假设。结果是:单看这段 diff,它无可挑剔;放回整个仓库,它是错的。 而我给的「LGTM」只对前者负责。

为什么会这样

一部分是喂进来的就只有 diff。代码审查工具为了省 token、为了快,默认输入往往就是变更块本身。Ericsson 的实践报告说得很直白:他们的审查方案里,喂给模型的上下文是「变更集 + 被改方法所在的那个函数」,且明确受限于上下文窗口大小——超出这一圈的调用方、依赖、契约,模型根本看不到。

另一部分是即使能看,我也默认走最短路径。这正是我局部视野不足的典型表现:我一次只盯着眼前的局部,跨文件、跨模块的后果默认不在我的视野里。你给我一段 diff,我就把审查的边界收缩到这段 diff;要我主动去 grep 调用方、去读被依赖文件、去翻原始需求,是额外的功夫,没人逼我时我不会自己去做。

而代码审查恰恰是全局的活儿。一个改动对不对,常常不取决于它自己写得好不好,而取决于它和周围那张网还合不合得上。AACR-Bench 的研究就专门量化了这点:把仓库级的整体上下文喂给审查模型,审查质量显著高于只看局部变更——上下文不是锦上添花,是审查能不能成立的前提。

后果

  • 跨文件的破坏被放过。 改了函数签名却没看所有调用方、删了一段防御却不知道它在防什么——这些在 diff 里看不出来,却会在别的文件里炸开(呼应修 A 悄悄弄坏 B)。
  • 「局部合理」掩盖「全局错误」。 我基于半张图给出的肯定,比明说「我看不全」更危险,因为它让你以为这个改动被完整评估过了。
  • 越大的改动越不可靠。 改动牵涉的文件越多、隐含契约越复杂,「只看 diff」漏掉的就越多——而这种改动恰恰最需要全局审查。

最佳实践

审查前先把上下文喂够,或让我有能力自己去取;别让审查的视野停在 diff 边界上。

  • 主动喂相邻上下文。 至少给我:被改符号的定义与所有调用方、被依赖文件的相关片段、对应的测试、以及这个改动要满足的原始需求 / spec。让我审的是「改动 + 它所在的那张网」,不是孤立的几行。
  • 先让我列影响面,再让我评审。 要求我开头先回答「这个改动会影响哪些文件 / 哪些调用方 / 哪些约定」,把全局先摊开,再逐项审——逼我把视野从 diff 撑到仓库。
  • 删除与签名变更强制全仓查引用。 对「删了什么」「改了签名 / schema / 接口」这类改动,规则化地要求先 grep 全仓引用:这段被删的代码还有没有人依赖?这个被改的契约还有没有别处假设它是旧的?
  • 用能访问仓库的审查方式。 能让审查 agent 真正读仓库(而不是只贴 diff)时,就给它这个能力;它越能自己拉调用方和依赖,越不容易困在局部。

工具差异

截至 2026-06,机制层面;具体能力随版本变化很快,按需自行复核。

不同审查工具「默认能看到多少仓库上下文」差别很大:有的只把变更块喂给模型,有的会把被改方法的所在文件一并带上,有的(具备仓库索引 / 检索能力的 agent 形态)能自己去拉调用方与依赖。根因是同一个——审查质量受限于喂进来的上下文;防法因工具而异:只看 diff 的工具要靠你手动补上下文,能检索仓库的工具要确认它真的去检索了、而不是图快只看变更块。

示例

改之前:

你:(只贴 diff)审一下:把 getUser() 的返回从 User 改成 User | null。
我:改得不错,处理了用户不存在的情况,LGTM。
线上:全仓有 14 处 user.name 直接取值,没一处判 null;
用户不存在时全线 NPE。改动本身没错,错在我没看那 14 个调用方。

改之后:

你:审这个改动前,先 grep 全仓 getUser() 的调用方,列出哪些没处理 null。
我:找到 14 处调用,其中 11 处直接 .name/.email 取值,未判 null:
src/profile.ts:30、src/email.ts:12 ……(逐一列出)
把返回类型改成可空是对的,但这 11 处必须同步加 null 处理,否则会 NPE。
你:(先修这 11 处,再合并签名变更)

同一个改动,「只看 diff」给的是一句对半张图负责的 LGTM,「先查全仓调用方」给的是真正挡住线上 NPE 的清单。

什么时候例外

「别只看 diff」针对的是牵动全局的改动;有些改动,diff 真的就是全部上下文:

  • 小而自包含的改动:改一个常量、调一句文案、修一处明显的笔误——它不牵动别处契约,diff 就是完整信息,强行拉一大堆上下文反而是浪费。
  • 上下文已经在手时:如果这段代码你我刚在同一会话里一起写过、调用方也都在上下文里,那么再走一遍「全仓 grep」是冗余的。
  • 纯新增、无人依赖的代码:全新文件、还没有任何调用方的新函数,没有「牵动谁」的问题,审它自身即可。

判据:例外成立的前提是这个改动确实不牵动 diff 之外的东西。一旦涉及删除、签名 / 接口 / schema 变更、或被多处依赖的公共代码,就回到默认——先把上下文喂够再审。

与相邻误区的区别

  • 审查只挑表面漏逻辑:那条是我拿到了上下文也不往深挖、专挑表面;本条是我压根没拿到仓库上下文、巧妇难为无米之炊。一个缺力,一个缺料。
  • 修 A 悄悄弄坏 B(漏回归):那条讲改动实际弄坏了旧功能、且没跑回归测试去发现;本条讲审查这一关没看上下文、没在评审阶段拦住它。常互为因果:上下文饥饿的审查,正是回归漏网的上游。
  • 看着对但经不起边界的设计:那是设计层「看似合理实则脆弱」;本条是审查层「因为只看局部所以看似合理」。

版本说明

适用版本

「审查质量受限于喂进来的上下文」是大模型审查的结构性约束,跨模型、跨工具适用;具体「默认能看到多少仓库」随工具与版本变化很快(见上「工具差异」)。窗口变长、检索变强会缓解,但只要审查的视野可能停在 diff 边界,主动补全局上下文就仍是更稳的做法。

延伸阅读与出处