返回博客

验证环路:把质量门禁从事后检查变成持续习惯

我以前对「验证」这件事的认知是碎片化的:改完代码跑一下测试,没报错就提 PR,心里默念「应该没问题」。这种做法能过关,直到它不能过关——某次上线时发现类型检查其实早就爆了,只是没人跑,或者某个 API key 被 console.log 给带出去了。

问题不是验证太麻烦,而是验证从来没有被结构化成一个「可以重复执行、结果可读」的过程。

六道关卡,串成一条线

验证的六个层次各自解决不同问题,但它们不是并列的——有先后顺序,前一道过不了,后面跑了也是虚的。

第一关:构建。最基础,也最容易被跳过。npm run build 失败意味着当前代码根本无法交付,后面所有验证都没有意义。失败就停,修了再继续。

第二关:类型检查。TypeScript 项目跑 tsc --noEmit,Python 项目跑 pyright。类型错误有轻重之分——有些是遗留的非关键 warning,有些是当次改动引入的真实漏洞。关键是要看 delta,不是看总量。

第三关:lint。代码风格和基础质量规则。单独列出来,是因为 lint 问题往往暗示结构性问题:一个函数太长、嵌套太深、未使用的变量——这些都是信号,不是噪声。

第四关:测试覆盖率。不是为了追一个百分比,而是在改动后主动确认「我改的这片区域,有没有测试保护」。改动波及到没有测试的区域,是真实的风险,需要人来决定接不接受。

第五关:安全扫描。主要是两类:硬编码的凭证或密钥(grep -rn "sk-" 之类)、不该留在代码里的调试输出(console.log 带着敏感数据)。这两类问题不会触发构建失败,也不会触发类型错误,靠人工审查经常漏——所以要自动化扫。

第六关:diff 复查git diff --stat 加上逐文件过一遍。目标不是再次验证逻辑,而是确认「改动的范围和我预期的一致」。经常能在这一步发现无意间动了不该动的文件,或者漏掉了一处该改的地方。

为什么要有「报告」这个动作

把六道关卡跑完之后,写一份结构化报告——每关 PASS/FAIL,问题数量,最终结论「READY / NOT READY」。

这个动作听起来像繁文缛节,但它解决了一个真实问题:口头的「应该没问题」和写下来的「构建 PASS、类型 0 错误、测试 94%、diff 3 文件」是两种完全不同的确认质量。前者依赖记忆和直觉,后者是可以被别人审查、可以在时间轴上对比的记录。

提 PR 时贴一份这样的报告,Code Review 的讨论质量会明显提升——审查者知道哪些门禁已经过了,可以把注意力集中在真正需要人判断的逻辑问题上。

持续验证,不只是 PR 前

验证不应该只在「要提 PR 了」才触发。长时间开发中,每完成一个有意义的单元——写完一个函数、搞定一个组件、完成一次重构——就跑一次验证环路。问题越早暴露,修复成本越低;更重要的是,你不会在最后关头面对一堆积压的报错,手忙脚乱地分不清哪个是本次引入的。

取舍与边界

验证环路的价值是「让已知的门禁不再靠记忆维持」,但它不能替代思考。几个边界需要清醒:

  • 测试覆盖率高不等于测试写得好。覆盖率是下限指标,不是质量指标。
  • 安全扫描只能捕捉已知模式的明显漏洞,复杂的逻辑安全问题仍然需要人来判断。
  • diff 复查是「确认范围」,不是「重新审查逻辑」——如果你发现自己在 diff 阶段做逻辑判断,说明 Code Review 没做好。

同样值得一提的是成本:在 Agent 辅助开发的工作流里,让 Agent 在每次改动后自动触发验证环路,相比「攒到最后一起跑」能节省大量调试时间,因为 Agent 生成的代码出错后能立刻得到反馈,不会沿着错误方向继续积累。

一句话心法

验证的价值不在于把问题找出来,而在于把「检查」这件事从偶发行为变成结构性习惯——问题本来就在那里,结构让它无处遁形。