在人工智能时代,代码验证为何比以往任何时候都更加重要

把访问令牌交给 Agent,它就会四处扩散:进入上下文窗口、进入工具调用日志、进入它在步骤间保存的笔记。每一份副本都能在任意位置运行,而且事后依然有效。
Relay 让凭证始终保留在 WorkOS。Agent 指明用户身份后,WorkOS 绑定该令牌、刷新它,并仅向允许的域名释放。被劫持的 Agent 会话只是进程,可随时终止。
「能运行」和「真正安全可信」之间的差距正在迅速扩大。多年来,编写代码是耗时费力的环节,而代码审查只是尾声的小任务。随着 AI 辅助编程的普及,这种平衡正在改变。
AI 工具现在能在数秒内生成可运行的函数,在数分钟内完成整个功能。团队每月产出的机器生成代码越来越多。换言之,写代码变得快速且相对容易,而代码验证反而更难了。审阅者仍需阅读变更、理解其逻辑,再决定是否上生产环境。写得越多,意味着需要验证的代码也越多。
我们近日有机会与 Andrea Malagodi 交流,他是 Sonar 的 CTO(这是一家开发了多款主流代码验证软件的公司)。他深入分享了关于代码验证的见解,尤其聚焦 AI 背景下的挑战,以及 Sonar 如何应对这些变化。
本文将探讨代码验证如何运作、AI 生成代码的崛起为何对其施加更大压力,并结合 Andrea 极具价值的观点,展望未来的可能走向。

正在发生的变化
从数据上看,代码生成与验证方面的变化相当明显。最清晰的信号之一来自 Google 的 DORA 研究——这是一项长期追踪数千个团队如何开发和交付软件的调查。他们最近的研究发现,团队采用 AI 越多,交付稳定性反而下降了。开发者对 AI 生成代码的信任度依然很低,超过三分之一的受访者表示对这些工具产出的代码没什么信心 [2]。换句话说,写代码的速度提上去了,压力却转移到了后续环节。
METR 研究团队的一项对照试验也得出了类似的结论。参与者是经验丰富的开源开发者,在自己的成熟项目上工作,每个任务被随机分配为允许或禁止使用 AI 工具。开发者原本预计 AI 能让他们提速约四分之一。
但结果完全相反:使用 AI 辅助的任务耗时反而多了约 19% [3]。更值得注意的是,这还是在开发者主观上认为 AI 提升了效率的前提下发生的。实际上,大量额外时间花在了写提示词、等待输出、阅读结果和修正错误上。客观地说,同一团队后续发布了一份更令人困惑的报告——部分原因是开发者仍然愿意继续使用 AI 工具 [4]。
不过,把这些结果放在一起看,结论很明显:AI 确实提高了代码的产出量,但同时也让后续的验证工作变多了。
那么,我们先来弄清楚代码验证到底是什么。
赢得信任
Code verification(代码验证)是涵盖所有检查工作的总称,用于确保一段代码是否正确、安全且足够稳定,以便上线交付生产环境。换句话说,它是为了获得足够的信任,从而将变更推向真实用户的过程。这里需要留意一个关键词——“earning”(赢得)。因为信任不是一蹴而就的,它是逐步积累的,每一次检查都只是在加固信任,而非一次性授予。
想象一下起草合同的任务。撰写文字只是其中一部分,而审查、法律审核和签名等环节,才将这些文字转化为人们真正可以依赖的东西。编写代码同理。当一段代码离开编辑器并提交到代码仓库时,它就隐含了一项关于功能正确性的声明。Code verification 正是测试这一声明的过程,直到团队有信心在真实的生产环境中使用这段代码。
某些领域通过严格的形式化验证将此过程推向极致。在这些领域,工程师必须用数学方法证明所部署的代码与精确规范完全一致。这类流程在飞行控制系统和内核等关键领域中已成标准,因为其中的单个缺陷就可能危及生命。然而,对于大多数软件而言,采用类似方法的成本往往远超其收益。因此,团队通常会选择成本更低的、分层组织的检查方式。
The Filter Stack
延续分层的类比,我们可以将 code verification 想象成一道过滤器堆栈。正如你所见,每个过滤器都能捕捉到特定类型的问题。

在堆栈的最顶层,是我们成本最低的检查,例如:
Type Checker:它确认在代码中流动的值是否与每个操作所期望的类型完全一致。这样,它甚至能在代码运行之前就捕捉到一整类错误。
Linter:扫描可疑模式和风格问题。
这类检查可以瞬间完成,几乎无需成本。在这一层之下,是我们的测试体系。
单元测试用已知输入运行一小段代码,确认其是否返回预期输出。测试能够捕捉类型检查器无法发现的代码行为错误,因为某些代码即便类型完全合法,仍可能计算出错误结果。例如,一个本应相加两个数字的函数,却错误地执行了乘法。在这种情况下,类型检查器和 Linter 都不会报错,只有将运算结果与已知答案进行对比的单元测试才能揭露此类错误。
在测试层之下,是人工代码审查这道防线。这通常由其他开发者审阅变更内容,判断其是否符合系统架构、是否解决了正确的问题,以及代码是否易于阅读。这一层能够捕捉机器容易忽略的问题,比如虽然能正常工作、但实现方式不符合团队规范的代码。
在所有这些层之下,是生产环境监控体系。该系统的职责是在真实流量下观察代码运行状况,标记出可能已穿透前面所有防线的潜在问题。
实际工程中的过滤器层级可能比上述更多,比如安全扫描器和依赖项检查等。但核心观点在于:每一层过滤器都针对其上一层的特定盲区进行补充。这就是为什么严谨的团队会在将代码发布到生产环境之前,按特定顺序部署多层防护。
静态分析与动态分析
这套过滤器可分为两大类别:
静态分析:这类过滤器在不执行代码的情况下直接检查源代码,因此分析速度快、覆盖范围广。借助静态分析,我们可以在一次扫描中检查整个代码库。类型检查器和 Linter 就属于此类。其代价是运行时的一些真实行为仍难以完全观测,因此静态分析有时会对实际运行中并不存在的问题发出误报。
动态分析:这类过滤器使用真实输入运行代码并观察结果。单元测试属于此类。它依赖于对实际行为的检验,但受限于测试所覆盖的执行路径。如果测试套件只覆盖了常规正常流程,就无法发现当输入为空时可能触发的崩溃问题。

尽管有全面的覆盖,干净的扫描结果加上全部通过的测试,仍可能留下漏洞。这就是为什么代码验证需要多层过滤器协同工作才能有效。
误报
一个诱人的结论是:检查越多越好。但代码验证的核心其实存在一个权衡。每个过滤器都可能犯两种错误:
误报(False Positive): 代码本身没问题,却被标记为有问题。
漏报(False Negative): 真正的 bug 溜过去了,工具却毫无反应。
如果把工具调得什么都要抓,开发者就会被海量误报警告淹没;但如果只在非常确定时才报警,又可能漏掉真正的缺陷。这两者互相牵制。
误报的代价相当高。当工具频繁误报时,开发者会开始无视它,而这种习惯可能是灾难性的——偶尔出现的真实警告也会被一并挥手略过。关于静态分析工具的研究描述了这种模式:过高的误报率会侵蚀信任,直到团队干脆关掉工具或不再理会它的警告。但这样一来,工具本想阻止的 bug 又有了可乘之机 [7]。

这正是为什么一套优秀的代码验证体系必须同样重视信号质量和覆盖率。Sonar 的 Andrea 将这种平衡形容为近乎代码验证领域的 CAP 定理——其核心理念源自一个经典结论:某些属性的强化往往以牺牲其他属性为代价。在代码验证中,三大竞争优先级是速度、准确性和覆盖率,没有哪个工具能同时完美占据三者。Andre
