让 AI 代码审查结论由验证证据决定
文章提出用固定规则汇总类型检查、测试、构建和契约差异等证据,区分失败、证据不足、部分验证与通过。每项证据保留复现命令和原始输出,模型推理只能作为待验证假设。
AI 深度解读
AI 代码审查给出「通过」,依据到底是检查真的跑过,还是模型把结论写得很有说服力?Powerduck 的判定方式是:模型负责提出线索,最终结果由固定规则根据实际证据计算,缺失检查不能被一段流畅的总结补成通过。
它值得读的地方,是把日常 AI 编程中容易混淆的三件事分开了:模型认为没问题、命令执行成功、要求的检查全部完成。只有最后一件事,才够得上它定义的 Pass。
模型可以提出怀疑,但不能签发通过
作者把模型单独提出的判断称为假设(hypothesis)。模型可以建议检查某段逻辑,却不能仅凭这条建议或分析,让检查变成已通过。
模型的语言再肯定,也不能替代实际检查。
最终判定不让 LLM 参与,而是使用固定规则处理收集到的证据:
- Fail:存在漂移(drift)或失败证据。
- Inconclusive:没有已验证的证据,空结果不能算通过。
- Partial:只验证了部分必需检查。
- Pass:要求的证据全部完成验证。
这里有两个边界。首先,部分检查成功与存在失败证据不是一回事,失败不能被其他成功结果抵消。其次,未知状态必须保留,不能因为没有报错就归入通过。
这样做能保证同一份证据得到同一个结论,也能防止模型在总结时把遗漏的检查描述成成功。不过,原文没有解释 drift 的具体定义和检测方法,不能自行把它等同于代码变化或环境变化。
另外,原文提出有「四类证据」,却没有完整列出四类的名称和定义,只明确说明了模型单独提出的判断属于假设。它给出的四种判定结果,也不能直接当作四类证据。
每个结论,都应能追溯到命令和原始输出
作者要求每条证据同时携带两样东西:产生结果的可复现命令,以及命令返回的原始进程输出。
这让审查从「相信一个标签」变成「检查标签的依据」。开发者可以重新执行命令,再把输出与记录比较。如果两次结果不同,可能是审查上下文已经变化,应重新运行检查,不能直接相信其中一次。
按照作者的规则,没有命令、没有输出的条目不算已验证证据,不能支持 Pass。Powerduck 展示证据类别、执行命令和原始输出,就是为了让这条判断链可供核查。
不过,可复现命令不自动保证环境可复现。原文没有交代依赖、环境变量等执行条件如何记录,因此它证明的是结果有据可查,还没有充分说明跨环境重跑的一致性。
lint 和 typecheck 都绿了,为什么仍然是 Partial
原文给了一个具体案例:一次改动通过了 lint 和 typecheck,但项目没有测试脚本。
此时,两项静态检查有证据,动态验证仍然缺失,所以结论是 Partial。补上一条可运行的测试并重新执行后,动态证据才从缺失变为实际测量,判定随后才能更新。
重点在于「重新执行」。新增脚本、补上测试文件,都不能提前改变已有证据。
同样,动态检查即使运行过,没有执行到的分支仍然缺少证据。缺失项目脚本应记为未验证;部分验证应保持 Partial;完全没有已验证证据时,应是 Inconclusive。
这里也不能反向推导出「加一条测试就必然 Pass」。原文同时强调未执行分支没有证据,最终是否通过,仍取决于要求验证的范围是否完成。
我的判断
我赞成把模型建议和最终判定拆开。相比让 AI 阅读若干输出后直接给出总体评价,固定规则更容易解释,也更容易追查遗漏发生在哪里。
但确定性只保证相同输入得到相同输出,不保证输入完整,更不保证检查设计正确。如果必需检查清单漏掉关键行为,即使按规则得到 Pass,也只能说明这份清单完成了验证。
这套做法与常见 CI 门禁有相似之处:都依赖检查结果决定能否通过。文章更强调保留「未知」和「部分完成」,并把证据展示到每个发现上。至于是否降低漏报、节省多少审查时间,作者没有给出对照数据;文中的案例是规则说明,不能当作效果实验。
能拿来做什么
- 让 AI 审查前端或 Node 项目时,要求它把推测与执行结果分别列出,并为检查结果附上实际命令和原始输出。
- 给审查流程明确列出必需检查;缺少测试脚本时保留未验证状态,避免把 lint、typecheck 成功当作全部完成。
- 阅读测试结果时,同时查看哪些检查和分支没有执行;有成功输出,不代表要求的行为都已经验证。
- 复查旧结论时重跑对应命令;如果输出变化,就更新证据和判定,不沿用旧的 Pass 标签。