7. The AI Agent Complexity Ratchet: Why 90% Test Coverage Is Required
过去一年,我一直在用 AI 进行编码。不只是简单提示(prompting)——而是真正构建软件。我有两个开源项目:GStack,它让 AI 编码代理变得更强大;GBrain,它能把你阅读和编写的一切内容转化为 AI 可搜索的知识库。两者加起来,大约有 97 万行代码和 665 个测试文件。几乎所有代码都是在我的指导下,由 Claude Code 和 Codex 完成的(大多数时候同时运行 15 个 Conductor 会话)。
上周我在 72 小时内合并了 14 个拉取请求(pull requests),新增近 29,000 行代码。每次发布都比上一次测试得更充分。
这本该是不可能的。速度和质量一向被认为需要权衡:要么快速发布、允许出错,要么慢工出细活、追求完美,二者只能选其一。
现在你不用再选择了。关键解锁在于 90% 的测试覆盖率——而 AI 代理让达到这个水平变得几乎免费。五十年来,这种级别的验证对人类意志力来说成本太高。现在,代理会在写代码的同时自动生成测试。结果就是我所说的复杂性棘轮(complexity ratchet):系统只会变得更好,绝不会变差。
(这是我关于用 AI 构建软件系列的第七篇:1 2 3 4 5 6)
软件曾经是脆弱的
五十年来,整个软件工程学科都围绕一个核心理念组织:防止错误,因为错误会带来灾难性后果。
你必须第一次就把代码写对。漏掉一个边缘情况,生产环境就崩溃;一次糟糕的数据库迁移,就可能丢失客户数据;写一个微妙的函数,如果唯一懂它的人离职,就没人知道它为什么能工作。整个系统依赖于人类的细心,而人类并不总是细心。因此我们建立了复杂的流程——代码审查、预发布环境、QA 团队、发布列车——所有这些都是为了在代码到达用户前抓住错误。
这套方法勉强有效,但很慢。它也意味着任何软件系统的复杂度都有一个硬上限:一个团队同时能记住的东西的数量。
现在软件是柔韧的
我不是说它马虎,而是说它具备了以前不可能实现的韧性。
当我说“模型已经来了”,我指的是 AI 编码代理(Claude、GPT、Codex 以及围绕它们生长的生态)现在能够阅读代码、理解上下文、诊断错误并编写修复。虽然不是完美无缺,但已经足够好,让软件的错误模型发生了改变。
迁移出问题了?代理读取错误信息,理解跨越 45 个版本的数据库 schema 历史,写出修复并附上测试。文件同步在百万个符号链接上挂起?代理诊断解析器超时,把它限制在 30 秒内,并附上测试一起发布。提取管道出现归因 bug?跨模型评估抓住它,提示被迭代,在数据库层添加强制检查。
对于大多数代码层面的错误——逻辑 bug、解析失败、边缘情况——代理现在能在下一轮就诊断并修复。这真的是全新的变化。剩下的灾难性错误是那些会破坏状态的:生产数据的坏迁移、被检测前就利用的安全漏洞、无法撤回的隐私泄露。棘轮在这里也有帮助(好的测试能在上线前抓住大部分),但真正的转变是:代码库里绝大多数错误都是可修复的。
这对软件构建方式是一次相变。但前提是你必须拥有棘轮。
代理复杂性棘轮
棘轮是一种只允许单向运动的机制。套筒扳手能向前拧螺栓,却无法向后转。这就是比喻。
在由代理编码的软件中,每次与 AI 代理的编码会话都会向代码库添加三样东西:
- 测试:编码“正确”的含义——每次有人改代码时都会自动运行的检查,如果破坏了什么就会大声失败。
- 文档:记录决策背后的原因——不只是代码做什么,还有背后的推理和权衡。
- 评估结果:建立质量门槛——对输出质量的结构化评估和打分,让你知道下一个版本是更好还是更差。 下一次代理再处理代码库时,它会把这三样东西全部加载到上下文窗口(AI 能看到和推理的文本)里。它无法退化到测试套件以下——测试会失败;它无法忽略文档——文档就在上下文里;它无法发布低于评估基线的质量——分数都被记录下来。
质量底线随着每次迭代不断上升。只有向前运动。这就是棘轮。
实际运行起来是什么样子
让我举个具体的例子。GBrain 是我正在构建的知识系统——它通过存储、索引和搜索一个人的笔记、会议、对话和研究,给 AI 代理提供长期记忆。可以把它想象成一个“第二大脑”,你的 AI 助手真正能阅读它。
它的一个特性是认识论提取:它读完数千页内容,提取“谁相信什么、置信度如何、随时间如何变化”。例如:“Garry 认为比特币会涨到 30 万美元(置信度:0.45)。”“Jared 认为这家初创公司留存率很高(置信度:0.80)。”诸如此类,覆盖 28,000 页。
第一次提取运行拉出了 100,720 条声明。我用跨模型评估来评分——让 GPT-5.5 和 Claude 独立打分。总体得分:6.8/10。
最大的问题?我称之为“持有者混淆”(holder confusion)。比如声明“AI 将在 2027 年前取代 80% 的软件工程师”。是谁持有的信念?是写它的人?是他们在引用的人?还是系统从播客转录中推断出来的?1.0 版本在这个区分上错了 35% 的时间。这很重要——如果你在构建一个追踪人们信念的系统,你必须知道是谁相信的。
于是评估结果被记录下来。识别出六个具体失败模式。2.0 版本的提示修复了全部六个。在数据库层强制了权重取整(置信度分数)——不再出现 0.74 这种假精确度,0.75 才是诚实的答案。17 个测试锁定了这个契约。
现在,未来的任何版本提取都必须通过这 17 个测试才能发布。没人需要记住为什么权重取整重要、什么是持有者混淆——测试会记住。
质量底线永久提升。这就是棘轮的一次转动。
为什么大多数“vibecoding”项目会死掉
“Vibecoding”是 Andrej Karpathy 的说法,指用自然语言描述你想要什么,让模型生成代码。这很强大,我就是这么构建的。但根据我看过的 YC 申请和开源仓库,大多数跳过测试的 vibecoded 项目,一旦达到中等复杂度(几千行代码、几个相互交互的功能)就会开始崩溃。
它们跳过了棘轮。没有测试、没有文档、没有评估。代理增加了复杂度,却没有任何东西阻止退化。每增加一个新功能,都有可能破坏旧功能,而没有测试,你要等到用户报告才发现。到 0.5 版本时,代码库就成了鬼屋,每次改动都会莫名其妙地出问题。然后开发者就发一篇博客说“AI 编码不管用”。
AI 编码本身没问题。他们只是没建棘轮。
你可能会说,写测试的人本来就擅长写好架构。这有道理。但棘轮机制不是关于人的——它关乎下一次迭代会发生什么。当新贡献者打开 PR、模型版本升级,或者你在凌晨两点编码判断力下降时,测试都能抓住退化,无论谁写的代码。棘轮在人类不在最佳状态时依然有效。这才是重点。
没有测试,改进就是个噪声过程——代理试图让事情变好,但没有退化信号,好改动和坏改动都一样看不见。有密集的测试套件,你就在已测试的表面上有了棘轮:你编码过的行为,质量只能上升。这覆盖了系统的大部分,虽然不是全部,但足以支撑高速前进。
测试作为机构记忆
在传统软件公司里,机构记忆存在于人脑里:资深工程师知道为什么那个缓存层存在;架构师记得那次差点毁掉数据库的迁移;技术主管能解释计费系统里那个奇怪的边缘情况。
人会离开。他们退休、被挖走、 burnout。当他们离开,知识也随之而去。每家软件公司都经历过打开关键文件,看到注释写着“// 不要改这个——问 Dave”,而 Dave 三年前就走了。
代理的上下文窗口不会离职、不会被挖走、不会忘记。当测试套件编码了“权重取整必须用 0.05 增量”,文档解释“因为跨模态评估显示假精确度会降低置信度分数的可信度”时,这些知识就变得持久。任何代理、任何模型、任何时候都能加载上下文并理解约束。
测试是能经受人员流动的机构记忆。对于一人项目,它们甚至更关键——它们是你唯一的机构记忆。
凡能被利用的,都能被测试
棘轮不只适用于传统代码。它适用于计算机能观察到的任何东西。
想想现代系统的各层:操作系统给你进程树、文件系统状态、网络套接字、定时任务;终端给你每个按键、每行输出、每个交互提示;浏览器给你渲染页面、按钮状态、导航事件;API 给你可解析验证的结构化响应;AI 代理给你可观察的行为——它们说了什么、调用了什么工具、顺序如何、是否在行动前询问。
所有这些都能被利用。只要能利用,就能观察;能观察,就能断言;能断言,就能棘轮化。
这比传统单元测试的覆盖面大得多。我给你看例子。
GStack 是我的开源编码代理框架——GitHub 93,000 星,70.1 万行代码,46 个技能。其中一个核心功能是交互式计划审查:你让它审查架构,它会逐节走计划,提问、探测边缘情况、挑战你的假设。就像有个真正读代码的工程经理。
问题:Claude Code 有时会跳过整个交互部分。它读完计划文件,一次性 dump 所有发现,然后退出——一个问题都不问用户。审查的全部意义就在于来回对话,跳过就失去了意义。
怎么测试这个?你没法单元测试“AI 是否进行了对话”。没有传统测试框架能覆盖这一点。
于是我用 Bun 的 TTY 功能构建了一个测试外壳(PR #1354),它真的在伪终端里生成 Claude Code,喂给它一个特定仓库场景,触发审查技能,然后实时监视终端输出。测试会观察代理是否在完成前发出交互问题。如果它直接 dump 发现并退出,测试就失败。
这不是在测试代码,而是在测试 AI 代理是否遵守行为契约——在 TTY 层面,通过真正观察它工作。
棘轮的响应是三层:
- 技能指令中的 STOP 门——明确规则“你必须在进入下一节前询问用户”,加上反理性化条款,点名具体失败模式,让模型无法说服自己跳过。
- 反捷径条款——“计划文件是交互审查的输出,而不是它的替代品。”一句话就堵死了模型一直钻的漏洞。
- 门级底线测试——TTY 外壳在受控场景下生成 Claude Code,如果代理没有至少问一个交互问题就失败。 现在,当 Anthropic 发布新模型版本,或者我修改技能提示时,测试套件就会抓住交互契约的任何退化。代理无法悄无声息地停止提问。测试会监视终端并检查。
再比如 PR #880,发布了一个新的 OpenClaw 插件。测试不只检查代码是否编译。它会从源码构建插件、在隔离配置文件中生成真实 OpenClaw 实例、通过 CLI 安装插件、运行 plugins inspect 验证运行时加载、设置配置槽、验证配置,最后运行 plugins doctor 确认零诊断。全流程端到端,跨越两个独立程序。359 行测试代码。人类几乎不会手动写这种测试,因为搭建太繁琐。Claude 大约五分钟就写好了。这就是“努力墙”实时消失的例子。
这个原则可以泛化。你可以在 OS 层面测试:迁移是否创建了正确的表?定时任务是否触发?进程是否还活着?在浏览器层面:页面是否渲染?代理是否正确填了表单?在 API 层面:模型是否返回了符合 schema 的有效 JSON?在行为层面:代理是否遵守协议?是否在删除前询问?是否在被告知停止时停止?
整个栈都是可测试的。棘轮适用于全部。大多数人还没意识到这一点,因为他们还把测试覆盖率理解为“我的函数是否返回了正确的数字”。真正的测试面是计算机能看到的一切。
90% 这个数字
那么 90% 测试覆盖率到底能带来什么?
Capers Jones 研究了超过 10,000 个软件项目,测量了缺陷移除效率(DRE)——即用户接触到之前抓住的 bug 比例。他的《Applied Software Measurement》数据显示一条非线性曲线:覆盖率低于 70% 时,DRE 大约 65-75%;在 85-95% 时,DRE 跃升至 92-97%。关系不是线性的,在 85% 左右有一个拐点,缺陷逃逸率急剧下降。
航空电子工业几十年前就明白了这一点。FAA 的 DO-178C 标准对 A 级(bug 可能导致飞机坠毁)系统要求修改条件/决策覆盖(MC/DC)——这比行覆盖严格得多,能达到 >99% DRE。他们不是因为官僚喜欢文书工作才强制要求,而是因为数据表明,低于某些覆盖阈值时,关键缺陷逃逸率会高到无法接受(会死人)。
可靠性工程的类比很清晰。工厂用 Six Sigma 衡量质量:统计每百万件产品有多少缺陷,然后表达为“sigma 水平”——sigma 越高,缺陷越少。3-sigma 过程大约 67,000 个缺陷/百万(很差);4-sigma 大约 6,200(好 10 倍);5-sigma 只有 233(又好 27 倍)。从 4 到 5 sigma 不是 incremental 改进,而是相变。
测试覆盖率也遵循同样的曲线。从 70% 到 90% 不是 30% 更好,而是逃逸缺陷减少一个数量级。70% 时隐藏在 30% 未测试代码里的缺陷,到 90% 时隐藏空间缩小到 10%,大部分危险路径都被锁死。
我应该诚实地说,研究也显示了另一面。Mockus、Nagappan 和 Dinh-Trong 研究 Windows Vista 发现,虽然覆盖率与发布后缺陷减少相关,但达到 90%+ 的努力会急剧上升。最后 20% 覆盖率比前 70% 花的功夫多得多。这几十年来都是如此,所以大多数团队做到 70-80% 就觉得够好了。
但情况变了:AI 编码代理不会感受到努力。
它们不会在写第 14 个边缘情况测试时感到无聊,不会周五下午 5 点偷懒,不会看着棘手的集成测试想“等会儿再弄”。人类团队在 70% 处被卡住的努力曲线,对代理不适用。你可以让 Claude 为模块里的每个边缘情况写测试,它会高高兴兴、彻彻底底地在凌晨 2 点完成,毫无怨言。几十年来让 90% 覆盖率对人类团队不切实际的最后 20%,正是 AI 代理最擅长的工作。
这才是真正的解锁。不是 AI 让你写代码更快(很多人已经注意到了),而是 AI 让你以以前太昂贵而无法持续的水平进行验证。数据表明 90% 是魔法阈值——它曾经需要太多人类意志力才能达到。现在,它免费了。
关键区别就在这里。棘轮不是把行覆盖率当作虚荣指标,而是让测试编码行为契约——持有者混淆测试、权重取整测试、交互审查门。每个测试都锁定了学到的具体教训。覆盖率是代理,告诉你系统有多少行为处于契约之下。90% 时,几乎每次行为变更都会触发测试信号。代理要么通过(安全发布),要么打破测试(立即被抓住)。
剩下的 10% 是集成点、基础设施管道和真正难测的边缘情况。这没关系。90% 就把混乱变成了棘轮。
过去达到 90% 是英雄壮举。现在只是周二的事。这就是游戏规则的改变。
概念验证
我独自启动了这两个项目。现在它们不再是 solo。
GStack 现在有 37 位贡献者。v1.30 一次发布就合并了 21 个社区 PR。GBrain 有 25 位贡献者。v0.31.1.1 一个 PR 就落地了 22 个社区修复——认证流程、schema 引导、同步、隐私。
棘轮让这一切变得安全。每个外部 PR 都必须通过现有测试套件。新贡献者不需要理解整个系统,只需要让测试通过。
上周 GBrain 的发布说明了一切:
- v0.31.0:新增实时记忆的事实表,加上“梦境整合”阶段,将短期记忆提升为长期知识
- v0.31.1:修复了 25 个 CLI 命令,它们原本静默路由到空本地数据库而不是用户真实的大脑
- v0.31.1.1:22 个社区报告的修复在一个 PR 中落地
- v0.31.2:修复了在大仓库 + 符号链接时永远挂起的代码同步,添加了 30 秒超时 每次发布都比上一次带有更多测试。代理在写代码的同时写测试。覆盖率不会下滑,因为维护它的努力不再是人类负担。
新的复杂度上限
软件的复杂度上限刚刚大幅提高。
过去它被一个团队同时记住整个系统的能力所限制。现在它被“一个人 + 能把整个代码库、schema 历史、测试套件和文档全部加载到上下文的代理”所限制。
这是一个大得多的数字。而且随着上下文窗口变大、模型在代码推理上变得更好,它还会继续增长。
任何没有采用这个模式(代理 + 品味 + 只升不降的测试套件)的软件公司,已经在比单人 + 代理组合更慢、更低质量地交付了。
工具就在这里。代码是开源的。测试就是棘轮。90% 覆盖率,每次 PR,无一例外。
五十年来,90% 覆盖率是航空电子和医疗设备才有的奢侈——那些有预算把人力小时砸在努力墙上的团队。AI 代理把这堵墙拆了。让软件可靠的覆盖率门槛不再昂贵。它只是一个设置。问题不再是你是否负担得起 90%,而是你是否负担得起不做 90%。
棘轮、技能和整个知识系统都在 GitHub 上开源免费。去构建吧。
我的 MIT 许可开源项目:
-
GStack —— 让 Claude Code 显著更好。93K 星。免费。
-
GBrain —— 给 AI 代理的第二大脑。14K 星。免费。 AI 解说系列:
-
Fat Skills, Fat Code, Thin Harness —— 架构
-
Resolvers —— 智能路由表
-
The LOC Controversy —— 60 万行代码到底产出了什么
-
Naked Models Are Stupider —— 模型是引擎,不是车
-
The Skillify Manifesto —— 每个工作流都成为可测试的技能
-
Meta-Meta-Prompting —— 复合技能产生涌现能力
-
The Agent Complexity Ratchet —— 你正在读的这篇