21  这套东西的边界

这是最后一章,它的作用是把我讲的这套东西的适用范围划出来, 以及列出还没有被证明的部分。一本讲”怎么建立可信判定”的书, 如果对自己的主张不给出判定边界,那它就违反了自己的第一条原则。

21.1 三个还没有被证明的东西

21.1.1 一、缺陷逃逸率没有被测量

书里所有的指标都是过程指标:拦截率 27%、结构检查失败率 7.0%、 中位转绿 1.4 小时、合并 2,675 次、成功率 98.3%。 它们全都是”检查在触发”的证据,没有一个是”拦对了”的证据。

真正能验证这套体系的只有一个数:缺陷逃逸率, 也就是合并之后才被发现的问题占多少。这个数不在, 而它的缺席意味着一件具体的事 —— “27% 的改动被拦过”和”拦下的都是文件超过 200 行”可以同时成立, 而现有的数据没法区分这两种情况。唯一走完全程的那个例子里, 被拦下的正是文件健康度那条规则。

有意思的是,能用来算这个数的数据全都在手边: 崩溃扫描每 5 分钟在跑,自动归因已经在做, 缺的只是把崩溃反向关联到引入它的那次改动。 更好的一件事是,有一个天然的对照组存在过—— 第一条自动检查比 Agent 规模化晚了两个月(小节 15.5), 那两个月的合并和之后的合并,是一个现成的前后切片。

21.1.2 二、负载类型限制了结论的可迁移性

这套系统跑的是 26 个消费级 App,而这类产品有两个性质, 恰好让这套方法特别有效。

第一是质量下限的容忍度。 一个剪辑工具偶发的界面错位, 和一个支付系统的金额计算错误,不是一个量级的事。 书里的方法在两种场景下都能用,但它们的收益曲线不一样 —— 在容错低的领域里,判定的密度需要高得多, 而”报数模式跑两周再拦”这类做法可能根本不可接受。

第二是产品之间耦合度低、影响面天然可切割。 “产品互不依赖”本身就是一条被强制的规则, 而这恰恰是稀疏测量能生效的前提 —— 依赖图之所以能大幅缩小测量范围,是因为改动的影响面本来就是局部的。

一个 300 万行但只有一个产品、模块间深度耦合的系统会怎样? 每次改动的影响面可能覆盖大半个仓库,稀疏测量的收益会大幅下降; 章节 17 那套增益与延迟的分析仍然成立, 但结论会不同 —— 那种系统的出路更可能在”降增益”那一支。 我不知道那种系统会怎样,而说不知道比编一个答案好。

21.1.3 三、规则集的长期腐化没有被观察到

被观察到的窗口只有半年多。小节 15.7 提出的三个问题 —— 规则怎么退休、重构时路径范围怎么跟着改、报数模式的规则谁负责收敛 —— 它们的答案要再过两年才知道。 而且现在连问题的严重程度都测不出来, 因为绕过率(小节 15.6.1)还没有被测量。

21.2 什么时候不该用这套

小节 10.4 已经列过四种:项目活不到规则回本、团队还没就 “什么是对的”达成一致、单人且不用 Agent、误报率压不下来。那四条不再重复。

读完第四部之后可以再加一条,而它比前四条更难识别: 需求本身高度不确定的时候,瓶颈在参考输入,不在判定章节 20)。这时候加固判定是在优化一个不是瓶颈的环节 —— 而它之所以难识别,是因为判定这一侧的所有指标都会变好看, 小节 21.11 会把这种情况单列出来讲。

21.3 错模型的代价

回到 章节 2 那个编解码器模型。 它生成了正确的行为 —— 我描述的整套系统是在那个模型下建成的, 而且建对了。这说明一个错的模型也可以生成正确的行为, 只要它错的方向凑巧指向对的一侧。

但错的模型有代价,而且这个代价在书里已经显现了三处。

它解释不了这套系统最好的那个抽象。 载体分类(章节 8) 之所以有效,是因为注意力分布、位置效应、上下文与先验的强度之争, 而这些跟解码的确定性一点关系都没有 —— 一个解释不了自己最强发明的模型,也预测不了下一个发明该往哪找。

它预测不了语义失败。 编解码器的噪声是局部有界的:坏一个块,烂一片。 模型的失败是全局自洽且自信地错—— 它交出一个格式完美、 看起来合理、整体错误的实现。书里的解药(变异验证、假绿检测) 是从经验里长出来的,不是从那个模型里推出来的。

它会把路线图指向错误的一半。 如果不确定性是解码器的属性, 前沿就是更好的解码;如果它是规格问题,前沿是更便宜更密的验证 —— 而后者才是这套系统实际在建的东西。

所以第四部不是学术装饰。换一个能预测的模型, 比守着一个能解释的模型值钱—— 而这个差别只有在 你需要知道”下一步该往哪走”的时候才显现出来。

21.4 这套东西最明显的一处缺口

如果只能指出一处,是 章节 19 那一处: 判定覆盖到代码,但没有覆盖到生产这个判定的工具链自己。

一条定时任务挂了四个月没人发现,5 条工作流从注册后一次没跑过, 磁盘容量问题重复了五次才被提到机制层面。这三件事的共同点, 用我自己的话说:它们都不产生判定, 而一个不产生判定的动作,坏了你不会知道。

21.5 我希望它被怎么用

三种用法,价值递减。

最好的用法是当成一份形状清单。 七个形状(章节 4) 是书里唯一完全可迁移的部分,它们不依赖任何技术栈、任何规模、任何工具。 在自己系统里指认出三个实例,门槛比”照着建一套判定系统”低得多, 收益也来得快得多。

次好的用法是当成一份判据清单。 书里有一批可以直接用的判据, 它们的共同特点是把品味问题变成事实问题

判据 用在哪
这一层被第二个实例挣得了吗 目录
这条规则换来了什么可测性 架构
这条规则什么时候需要到达 载体
这道检查坏了会表现成通过还是失败 判定
出错之后重试能不能挽回 风险等级
同一个判断你做过几次 该不该建机制

六条判据,每一条都能在十分钟内用完一遍。 它们用到的术语,附录 D 里按使用场景分组索引了一遍。

最差的用法是照着抄那二十三条规则。 小节 15.5 讲过为什么:抄来的规则边界没有被你的代码校准过,误报率会高, 然后被绕过,然后连带损害你对整套东西的信任。

21.6 三件我没做到的事

对称地,我也该给自己划一条判定边界。

它没有证明这套做法值得。 小节 21.1.1 讲过, 所有数字都是过程指标 ——“这套东西让质量变好了”这个命题, 书里没有证据,只有”这套东西在按设计工作”的证据。

它的样本量是一。 一个仓库、一个人、半年多, 所有的”这样做有效”都是从这一个样本里读出来的。 其中哪些是普适的、哪些是这个特定环境的产物, 书里的划分是推理,不是数据。

它的理论部分是后加的。 第四部用控制论重述了一套已经建成的实践, 这个重述是有价值的(它预测了几件经验推不出的事), 但它没有经过检验 —— 那几条建议(振荡次数、观测器、增益调度) 一条都还没有被实施过。

21.7 接下来这套东西会怎么变

一个预测,以及做这个预测的依据。

判定这一侧的成本会继续下降:检查器会更快、更聪明、更便宜, 这是确定的趋势。参考输入的成本不会下降—— “该做什么”这个问题不会因为工具变好而变简单。

这两条合起来给出一个方向:判定和执行会越来越不是瓶颈, 而”该做什么”会越来越是瓶颈。对读者的实际含义是: 我讲的东西,投入回报期是有限的。

不是说它会过时 —— 七个形状和六条判据大概会长期有效。 而是说当你把判定这一侧建到”不再是瓶颈”之后, 继续投入的回报会迅速趋近于零,而那时候该停(小节 16.11)。 这个时刻会比大部分人预期的更早到来。

21.8 载体那一章为什么最通用

章节 8(载体)是全书适用面最宽的一章,理由有三条。

它零基建成本,任何规模的团队今天就能用 —— 不需要新工具,不需要改 CI,甚至不需要写代码。

它是唯一一个”分类本身就是答案”的章节。 其余每一章都在讲”这样做更好”,而那一章讲的是 “按这个维度分开,问题就消失了”—— 一个分类如果是对的,它会让原来的争论变得不必要。

它是这套系统里最不依赖具体技术的部分。 换语言、换构建系统、换 Agent,那五种载体的划分仍然成立。

如果只能保留一节,是 小节 8.3.2 —— “每条规则都要带着它的失败形态”。 因为它是唯一一条今天下午就能做完、而且明天就能看到效果的建议。

21.9 六条建议汇总

书里散落着几条对源系统的具体建议,集中列一遍 —— 它们的共同点是数据全都在手边,缺的只是把它接进一条判定:

# 建议 数据在哪 在哪章
1 把振荡次数做成时间序列 流水线记录 小节 17.7
2 给定时任务建观测器(测效果不测执行) 签入的证书等状态 小节 19.3.1
3 哨兵改成相对变化率 每次运行的扫描数 小节 18.5
4 测量绕过率 改规则的提交历史 小节 15.6.1
5 规则集的健康度自动化 每次运行的输出 小节 13.12.1
6 重构后主动检查规则的扫描数 同上 小节 6.11.2

六条里有四条用的是同一份数据:每次运行输出里的扫描数。 那个数现在只被用作一次性的哨兵比较,它的时间序列没有被保存。

这可能是全书里投入产出比最高的一条建议: 把每次运行的每条规则的扫描数追加到一个文件里 —— 一行代码,而它同时支撑了第 3、5、6 三条。

21.10 对读者的一个请求

书里所有的结论都来自一个样本(小节 21.1.2小节 21.1.3)。 如果你按书里的方法量了自己的系统,而结果不一样 —— 那是有价值的信息,而不是你做错了。

具体地说,下面四个数如果在你的系统里差别很大, 说明书里的某个前提在你那里不成立:基建故障占比(这里是 23.8%)、 零断言测试占比(这里是 4.0%)、 结构检查与端到端的耗时比(这里约 1:3)、 以及规则的绕过率(这里未知)。

第三个尤其值得对照—— 如果你的比例接近 1:1, 那么串级控制(小节 17.3)在你那里不成立, 而这一整套关于回路的分析需要被重新做一遍。

21.11 反过来问:什么情况下它会主动伤人

小节 21.2 列的是”不该用”,那是收益为零的情况。 这一节列的是更少被谈的一类:收益为负。

第一种是判定的可信度还没建起来就大量铺规则。 小节 16.14.4 讲过这个状态:你有二十条规则, 但不知道它们是不是还在工作。它比”只有三条可信的检查”更糟, 因为虚假的安全感会让人停止手工验证—— 也就是说,你不仅没得到二十条规则的保护, 还失去了原本有的那层人工兜底。这是一次净损失。

第二种是把品味写成了规则。 一条写不出失败形态的规则 (小节 7.5.1),它拦下的每一次都是一次 没有理由的摩擦。更贵的是它对旁边那些真规则的连带损害 —— 当一个人因为一条他不认同的规则被拦住三次之后, 他对整套规则的默认态度就变了。信任是一个共享的池子小节 3.9),而品味规则消耗的是所有规则的额度。

第三种是在参考输入是瓶颈的时候加固判定。 小节 17.15.3 那个稳态里, 判定已经不是瓶颈了,而继续投入的效果是 把一件不该做的事做得更快、更可靠、更彻底。 这一种最难被识别,因为所有的过程指标都会变好。

三种的共同结构值得记:它们都不表现为失败, 都表现为一切正常,而且都会让相关的指标变好看。 这不是巧合 —— 一个会让指标变差的错误做法, 会被自己纠正掉;能持续存在的错误做法, 都是那些让指标变好的。

21.12 读完之后的行动清单

按”今天能做完”到”需要几个月”排。

今天(半小时到两小时):断言测试执行数不为零(小节 16.6.1); 数一数你的常驻文件,标出写不出失败形态的条目(小节 10.6.0.1); 列出你系统里不可逆的动作(小节 10.6.0.2); 翻最近十次红灯,标注代码问题还是基建问题(小节 10.6.0.3)。

这周(半天到一天):退出码三分(小节 16.6.2); 重写常驻文件里的三条规则,每条补上五个部分(小节 8.24); 给最危险的一条路径写一份清单,打印不拦(小节 14.26)。

这个月(观察为主):记评审里重复说的话; 记每次 CI 红灯的分类和定位耗时(小节 11.13.1); 什么规则都不加。

三个月:第一条规则,报数模式;开始记录扫描数的时间序列; 量三个数 —— 测量延迟、回路延迟、振荡次数(小节 17.7)。 六个月:判断自己在哪个稳态(小节 17.15), 据此决定下一步。

注意”这个月”那一格里那句”什么规则都不加”—— 它是整张清单里最难执行、也最重要的一条, 因为它是唯一一条要求你什么都不产出的。

21.13 最后

把这些压成一段话:我们不要求 Agent 永远不犯错。 我们要求的是错误能在副作用发生之前被发现,原因说得清楚, 修正有明确方向,修完以后还能用同一套证据确认它真的变好了。 这个要求目前只对代码成立。

一个功能可以有很多种正确实现。只要它满足同一组边界, Agent 就可以在里面自由探索:行为能够被测试观察, 结构符合仓库的架构约定,改动尊重 owner 和不变量, 高风险动作保持显式,失败能定位、修复能复验。 不承诺同样的代码,只承诺同样的判定边界。

回到最开始那两个问题。环境决定 Agent 能不能做对, 靠的是把规则写进结构而不是写进文档 —— 代码库是它读到的上下文, 架构让仓库不随规模腐化,工作指导让规则在正确的时刻到达它面前, 工具链决定它的手能伸到哪里。检查决定我们能不能知道它做对了—— 测试看运行时的事实,结构检查看结构上的事实, 路径不变量看这条路径背后的约定。

第四部想说的是:这两半合起来是一个闭环控制系统。 前馈决定性能,反馈提供鲁棒性,传感器需要自己的故障管理, 测不到的状态需要观测器,而设定点永远在环外。

这四块环境里,工具链是唯一一块光靠约束长不出来的 —— 目录可以规定、依赖方向可以拦、规则的载体可以安排, 但”Agent 够不够得着”只能靠一件一件把东西造出来。 7.4% 的代码量花在这上面,是这个仓库做过的最不显眼、 也最难省掉的一笔投入。

如果要把这套做法压成一句话:把重复出现的判断变成机制, 把高风险的边界变成协议,把每一次失败变成可以行动的证据。

21.14 最后一句

我从一个具体的仓库开始,也应该在一个具体的地方结束。

那个仓库里现在有三百多万行代码、二十几个产品、二十三条规则、 二十份路径清单、二十八条定时任务, 以及其中五条从来没有跑过、一条挂了四个月没人发现。 这两组数字必须放在一起看,因为它们说的是同一件事: 一个系统在有判定覆盖的地方可以做到非常好, 在没有判定覆盖的地方会烂到你无法想象—— 而这两个地方之间,没有中间状态。

全书压到最后剩一句,不是那句”确定性不是 Agent 的性格”,虽然它更好听。 是这一句:

判定覆盖到哪里,确定性就只到哪里。

因为它同时是这套做法的方法和它的边界—— 它告诉你该往哪投入,也告诉你为什么那个挂了四个月的崩溃 一定会发生在没有判定覆盖的地方,而不是别的地方。

它最有用的形态是一个问题,可以对任何一个系统、任何一个角落问: 这里,什么东西在产生判定? 如果答案是”没有”, 那么那个地方现在是什么状态,你不知道—— 不管它看起来多正常,不管它跑了多久没出过问题, 不管建它的人多相信它。你不知道,而且你不会知道, 直到它以某种方式撞出来。

确定性不是 Agent 的性格,而是环境给它的边界。 把 Agent 包围起来,最终是为了让我们敢把更多事情交给它。