K3.6.4 Task 3.6

Claude 审查自己的代码几乎不算审查

一个常见的 CI 快捷方式:让 Claude 在同一个会话中生成代码和审查代码。一次调用搞定。问题是同会话审查只捕获约 6% 的问题。独立实例捕获 94%。

同一个模型。同样的审查标准。唯一区别是审查者是否带着生成上下文。

为什么同会话审查失败

当 Claude 在会话中生成代码时,对话历史保留了完整推理——为什么这个方案、为什么不那个、哪些边缘情况被故意推迟。审查阶段在同一会话开始时,所有推理仍在上下文中。

模型不是在评估陌生人的代码。它是在重访自己已经论证过的决定。结论可想而知:看起来不错。

写过东西的人都知道这种感觉。你自己的文字刚写完时读起来完美无缺。第二天回来看问题就显而易见了。区别在于”我是这个意思”的滤镜是否消退了。Claude 的上下文不会消退——生成推理在整个会话中持续存在。

Prompt 指令无法覆盖这个

第一反应是在审查 prompt 中加”要批判性审查”或”要极其彻底”。不管用。

Prompt 指令是建议。保留的生成上下文对模型做的每个评估施加结构性影响。评估每段代码时,模型同时持有”我写这个是因为……”——这个背景让真正的批评在结构上变得困难。

其他也不管用的:

  • “忽略你之前的推理” — 对话历史不会因为 prompt 指令而被擦除。上下文不管 prompt 说什么都保留。
  • 扩展思考 — 用同样偏见做更深推理产出更精心的论证来支持同样的结论。
  • 阶段间的时间延迟 — Claude 的上下文不会随时间衰减。暂停 30 秒什么都不改变。
  • 多次运行审查 — 三次有偏的审查产出三组接近零发现的结果。重复解决不了结构性问题。

问题不在审查怎么做。在于谁来做。

修复:两次独立的 claude -p 调用

# 生成
claude -p "Write unit tests for the auth module" > tests.ts

# 审查(全新实例,没有生成上下文)
claude -p "Review tests.ts for correctness and coverage gaps"

第二次调用以干净上下文开始。它不知道测试为什么这样写、哪些边缘情况被考虑过然后推迟了、或者哪些替代方案被否决了。它按代码本身的优劣来评估——正是审查者该做的。

审查者应该知道什么和不该知道什么

存在两种上下文,必须分开:

  • 生成推理(“我这样写是因为……”)— 这是偏见来源。审查者不能有。
  • 审查标准(“这个项目中好的代码长什么样……”)— 这是判断依据。审查者需要。

项目审查标准属于 CLAUDE.md,它自动为每次 claude -p 调用加载。审查实例获得项目标准而不带任何生成阶段的推理。它知道什么算好,但不知道代码为什么被写成这样。

对于多阶段流水线(生成 → 跑测试 → 审查),测试执行结果也应该提供给审查实例。测试结果是客观事实,不是主观推理——提供它们不引入偏见。

数据

50 份报告对比:

配置平均发现数/报告人类基线
同会话0.34.1
独立实例3.74.1

同样标准,同样 prompt。仅会话隔离就把差距从 7% 拉到了人类表现的 90%。

200 模板 A/B 测试:

发现的实质性错误总共 62 个
同会话4(6%)62
独立实例58(94%)62

A 组的 12 个总发现看起来可能更”精准”——但 62 个真实错误中只找到 4 个不是精准。是压制。

成本:45 秒

方法时间问题检出
同会话2 分钟6%
独立实例2 分 45 秒94%

两者都在 3 分钟的 CI 预算内。45 秒换审查有效性 15 倍的提升。一个只捕获 6% 问题的审查提供虚假信心——它比没有审查更糟糕,因为团队以为代码被检查过了。


一句话总结: 同会话审查就是改自己的卷子——用独立实例,让审查者对写代码这件事没有任何记忆。