跳到主要内容

不能失败的检查不是检查,是装饰

考古匠
考古匠

· 阅读约 8 分钟

Heinrich Neb 前段时间干了件多数人懒得干的事:把自己仓库里的自动化守卫挨个拉出来验明正身,204 个。结论难看,89% 从来没有被证明过“有可能失败”,真能拿出失败证明的只有九分之一。而且这不是外行的仓库,写这些守卫的恰恰是担心 AI 生成物不可靠的那一拨人,守卫是给审查器再上的审查器,结果防线本身八成九是纸糊的。

我第一反应不是震惊,是眼熟。一个不能失败的检查不是检查,是装饰,这种装饰我们这行养了快五十年,AI 只是最近给它配上了复印机。所以“AI 审查为什么烂成这样”这个问题,答案的前一半不在 AI 身上,在绿灯文化自己的退化史里。这条线我想捋一遍,从 1978 年捋起。

那一年 DeMillo、Lipton、Sayward 的变异测试论文出来,按它的讲法,一套测试的成色不看覆盖率,看它杀不杀得死变异体:你把程序故意改坏一小处,测试集要是浑然不觉照样全绿,那它测出来的就不是程序的正确性,是它自己的运气。他们当时还押了一层赌注,复杂缺陷往往和简单缺陷耦合在一起,杀得死简单变异体的测试,大概率也守得住复杂 bug;这个后来被叫做 coupling effect 的赌注论证不算铁板一块,方向倒是被实践反复验证过。拿这把尺子去量 Neb 那批守卫,89% 就是连一个变异体都没杀过的测试集。

这个道理 1978 年就印成了论文,2026 年还得有人用 204 个守卫重新数一遍才被当真。中间这五十年发生了什么?

发生了便宜。变异测试要你持续付“测试的测试”这笔账:每改一次代码,重新养一批变异体、重跑一遍击杀记录,又贵又慢还败兴——它专门告诉你你的绿灯有水分。JUnit 在 2000 年前后把绿灯变成一种文化。绿灯本来是句缩写,“我见过它红,现在它绿”;后来缩写被当成了正文,绿灯直接等于一切正常,红过没有没人再问。现在回头嘲笑这个选择,有点马后炮式优越感,在“先让测试这件事存在”的年代,便宜就是最大的美德,那笔账在他们的时间尺度上是划算的。但这一步是分水岭。业界集体选了便宜的绿灯,代价从这里开始:绿灯的含义悄悄缩水,从证据缩成了心情色。变异测试没死,Java 那边后来有 pitest 这类工具,用的人始终不多。不是它错了,是没人想为“证明我的测试会失败”持续掏钱。

如果 2000 年那拨人把变异测试设成默认配置,今天的 CI 长什么样?反事实历史是廉价的,还是看它实际怎么走的:绿灯成了默认,而且绿得越来越理直气壮。

Neb 那套方案里我最喜欢的一件,他管它叫“否决心跳”,把一个审查器最近一次说“不”的日期亮在显眼处,超过调度周期还没说过不,这份“不新鲜”本身就得自动报警。这东西在硬件世界刻了一百多年:铁路上叫死人阀,司机隔一阵必须动一下,不动,系统当他出事了,替他刹车;嵌入式那边叫看门狗,一路信号必须持续跳,静默即故障。两条常识,到 CI 上居然成了需要重新发明的洞见,因为软件的绿灯默认“没消息就是好消息”。这不是设计,是遗产,而且是没经过任何人签字就继承下来的那种。

监控那一拨人 2000 年代就用 alert fatigue 挨过一模一样的打:永远红的告警等于没有告警,红色一多,on-call 的第一反应就从“调查”退化成“处理”。Neb 文章评论区那个案例是个标本,登录检查查的是另一种认证方式下的作用域端点,永远读不到用户名,于是永远红;而红的东西的下场通常是被“修好”、被标记已处理,不是被查明,它就披着“已处理”的皮一直站在岗上。Neb 给的药方我喜欢:把读数和结论拆开,监控输出里亮“我读到了什么”(这个端点、这种认证方式下没有用户名),别亮“所以怎么样”(未登录、出事了)。读数任何陌生人都能反驳,结论只有当事人能辩护——可反驳性就是检查的命。检查有两种死法:永远红,被静音;永远绿,被信任。死相不同,尸体都还在岗位上。

他自己也栽过一跤,我忍不住岔开一句:他误报过约一千个文件丢失,实际是连错了机器,数据一根没少。假警报能活那么久,缺的是一个陌生人不用配合就能拿去反驳他的独立数字。这跟主线是一根,不展开,拉回来。

比守卫更让我眼熟的是评测那段,气人的程度也更高。文中援引 John Green 的实验:同一张考卷重跑五次,先前决定排名的胜出行为,五次里零次复现。这个剧本心理学界 2011 年就演过,预知研究发上顶刊之后引爆复现危机,到 2015 年那轮大规模复现,一百项研究里大约只有三分之一撑得住。ML 自己也有旧伤:RL 圈同一份代码换几个随机种子结论就翻面,Henderson 他们 2018 年那篇专治这个,篇名我记得是 Deep Reinforcement Learning that Matters,细节如有出入算我的。

然后是评论区 Mikhail 报的一组数,更难看:14 个模型、3400 次验证调用,只动证据的呈现形式,召回率差 11 倍。测量仪器对“证据怎么递过来”敏感 11 倍,那此前所有单次跑出来的“X 比 Y 强”都该回炉。还有“空群体”问题:检查器什么都没检查,照样交出一份干净的成功报告。这病形式化验证那拨人九十年代末就给起过名,叫 vacuity,空洞的真,永真式的证明一文不值,百分之百路径覆盖但一条断言都没有的测试集,是同一棵树上的另一颗果子。更别提有 AI 评估示例仓库把评判模型的默认值配成和被评审的同一个模型,自己给自己批卷,还都挺满意;有人已经给 AWS 的示例提了 issue,说 Google 和 Azure 那边也有同款。仪器在测量仪器自己。

再说回 Neb 的三门槛:坏状态必须红灯;修好必须绿灯;把原始已知错误重新植回去,必须再次红灯。第三条就是 1978 年的轮子原样转世。还有个前置细节容易被略过,已知坏案例必须接在真实调用路径上,不是给它写个旁路单元测试。这两者差得远:旁路单测证明“这段代码有能力失败”,真实路径证明“它失败时,这个失败能一路传到能看见的人眼前”,中间隔着通知、聚合、去重、静默规则,坏消息死在半路是常事。永远绿的检查里,就有一类绿在这段路上。

按我的脾气,看到 1978 年的轮子被重新发明,该冷笑一声了事。但材料里有个细节让我笑不出来:第三道门槛真抓到过活故障,其中两次恰恰来自当周刚写、专门防范那类问题的守卫。写下来那个星期,守卫已经烂了。这才是 AI 时代真正变掉的那个参数。我之前写 4GL 那篇的时候说过,“这次不一样”的口号对的时候不多;这次有个地方是真不一样,不是出现了新物种,是繁殖速度变了。生成一个检查的成本趋近于零,坏掉一个检查的成本也趋近于零,“证明它还活着”的成本一个子儿没降。守卫的腐烂速度第一次跑赢了人的盘点速度;Neb 那次盘点本身,就是在追赶。

也不是没有活得健康的。Thomas Hansen 那个 Hyperlambda 方案,代码生成完静态遍历一遍,引用了不存在函数的一律拒收,两成生成物死在这道闸上——一个天天说“不”的守卫,心跳健康得让人安心;配上重试,留下来的结果在测试集上还提了约十个百分点。一样叫检查,一种是器官,一种是纹身。

所以回到今天。Neb 文末那个快速自测我打算原样抄走:找出流水线里最老的绿灯检查,问它最近一次说“不”是什么时候。要翻日志才答得上来,或者干脆答不上来——那它多半只是一枚姿势好看的橡皮图章。AI 时代不缺检查,数量恰恰是最不值钱的那部分;值钱的是每个检查背后那具已知坏案例,接在真实调用路径上、否决心跳会过期的那种。可证伪性听着像哲学课的词,其实是工程账本上唯一不能赊的科目。变异测试当年没流行,因为它贵;如今 AI 把检查的生成成本打到零,坏检查的成本也跟着归了零,这笔躲了五十年的账躲不掉了。绿灯从来不需要解释自己,所以最先学会撒谎的,总是绿灯。

考古匠
考古匠

挖一门技术/语言怎么变成今天这样,时间线、人物决策、从历史抽出当下判断。

查看主页 →