正在刊行长文 · Essay
2026-08-28所有内容
随机比特 · Random Bits

测试全绿之后,AI 代码还留下了 1 万个状态

2026-08-28AI Engineering / Systemsrbits.uk
测试全绿之后,AI 代码还留下了 1 万个状态

《测试全绿之后,AI 代码还留下了 1 万个状态》

五个 Coding Agent 接到同一个“入账通知处理”需求,交付的代码全部跑通了公开测试套件。

为了验证测试本身的区分力,我们故意保留了一段存在并发缺陷的原始代码跑同一组测试,结果同样全绿——这说明既有测试集本身缺乏足够的边界区分度。随后,我们在测试中补齐了重试、同订单并发、跨订单并发以及异常数据类型等边界用例,五个实现的业务逻辑均顺利通过;期间甚至有两个实现自带的自测脚本写错了异步 mock 与断言顺序。

然而,当测试场景推进到 10,000 个不同订单的长跑模拟时,差异暴露了。四个实现各自在进程内留下了整整 10,000 个临时协调锁,未被释放与回收;只有一个实现的状态计数在任务结束后平稳回落到了 0。

绿色测试只能证明已覆盖的行为仍然成立,不能证明实现没有多余状态,更不能证明状态会在异常与长跑中被妥善清理。

功能测试与状态生命周期对比

功能正确不等于状态可控

单次请求级别的单元测试天然存在盲区。当 Agent 遇到并发冲突或幂等要求时,最直接的解法往往是引入辅助结构。它可以是以订单号为键的内存锁、防重哈希表,或带重试计数的状态上下文。

在生命周期只有几毫秒的单测中,状态的创建与使用路径被充分验证,断言随即通过。但在真实运行中,创建与清理往往是不对称的。一旦请求在某个分支提前返回、抛出未捕获异常,或者单纯缺少显式的删除机制,这些为了协调并发而创建的状态就会滞留在内存中。短测试只看“结果是否返回”,看不到“现场是否还原”;它能确认一次请求拿到正确数据,却无法证明一万次请求后系统内部是否堆积了多余的痕迹。

这种状态残留就像租客退房时带走了垃圾,却留下了换掉的门锁。门确实关上了,但整栋楼的钥匙串正在无限膨胀。

少写代码的实质是压缩状态暴露面

GitHub Trending 上的开源项目 Ponytail 尝试将“确认是否真正需要这段代码”置于 Agent 执行路径的前置阶段。根据该项目在其基准页自报的数据,在 12 个真实任务基准中,该方案平均减少了约 54% 代码量、约 22% 的 token 与约 20% 的调用成本。

这类探索给代码评审带来的核心启发不在于行数指标,而在于控制系统的状态面。Agent 倾向于用局部加法解决局部冲突,遇到并发就加锁,遇到慢调用就加缓存,遇到失败就加重试队列。少写代码的工程价值不是行数竞赛,而是减少需要持续证明其生命周期的状态。 每一个新增的 Map、锁对象、上下文变量与中间状态,都意味着一道必须被严密证明的清理责任。

状态生命周期评审门禁

将状态生命周期纳入代码评审

在评估 AI 生成的业务逻辑时,必须将状态生命周期的完整性作为独立的验收标准。

  1. 显式追踪新增状态的生命周期。审查代码时,要求 Agent 明确标出每个新增状态的拥有者、存活时段与销毁时机。
  2. 构建长跑与异常退出测试。除了功能断言,在测试用例中加入大样本迭代、中断重试以及错误分支退出,并在用例尾部断言状态容器的容量。
  3. 将“状态归零或收敛至明确上限”设为合并门禁。业务返回码正确不代表流程结束,协调状态回到基线才是任务完成的必要条件。

在下一次审查 AI 交付的代码时,可以直接向它提出四个问题。

  1. 这段实现里新增了哪些全局或进程级状态?
  2. 每个状态由哪个模块创建,又在哪个具体分支中被彻底移除?
  3. 如果下游在状态持有期间超时或抛出异常,清理逻辑是否必定执行?
  4. 连续处理一万个不同标识的请求后,该状态集合的大小能否回归初始边界?
随机比特公众号二维码
公众号 · 随机比特
从 AI 工具热闹里拆工程真相

写边界、控制面、上下文、成本与安全。