> 对抗夹具的覆盖单位是"夹具 + 参数",不是夹具——第 27 条子原因只在加了一个 flag 之后才响
一个论文可读性检查器有 9 项检查。它的自检先只要求"每项检查至少命中一次", 后来发现这个粒度抓不住东西:caption 这一项同时覆盖太短和缺失两个分支, 其中一个烂掉,另一个照样把计数顶在 1,自检继续绿,而它在真论文上已经瞎了一半。
结论先行¶
一个论文可读性检查器有 9 项检查。它的自检先只要求"每项检查至少命中一次",
后来发现这个粒度抓不住东西:caption 这一项同时覆盖太短和缺失两个分支,
其中一个烂掉,另一个照样把计数顶在 1,自检继续绿,而它在真论文上已经瞎了一半。
修法是把子原因显式登记:REASONS 列出 27 条
(检查, 消息正则, 子原因 slug),--json 给每条发现打上标签,
自检在对抗夹具上断言两件事 —— 每条登记的原因都要响,且不能出现 UNREGISTERED。
我按文档描述跑了一遍这个地板,用它自己的三个对抗夹具:
tripwire.tex 20 findings
tripwire_b.tex 11 findings
tripwire_c.tex 2 findings
declared 27 fired 26 UNREGISTERED 0
never fired: ['teaser/kind-exempt']
26/27。 差的那一条不是死代码。它是一条只能由参数触发的原因: "这类论文不需要 teaser 图" 要有一个论文种类才会说出口。 去翻自检脚本,它跑的不是三次,是四次:
runs = [(f, []) for f in sorted(glob.glob("fixtures/tripwire*.tex"))]
runs.append(("fixtures/tripwire_b.tex", ["--kind", "benchmark"]))
同一个文件,换一个 flag,再跑一次。补上这一次:
| 跑法 | 27 条里响了 | 没响的 |
|---|---|---|
| 3 个夹具,不带参数 | 26 | teaser/kind-exempt |
+tripwire_b.tex --kind benchmark |
27 | 无 |
为什么¶
对抗夹具的直觉是"造一份踩满所有检查的输入"。但一个检查的分支不只由输入选,
也由调用参数选。--kind 这类参数在代码里就是一条 if,
它背后那条路径没有任何输入能走到 —— 不管夹具写得多脏。
于是覆盖的单位必须是 (夹具, 参数) 这个二元组。按文件数夹具会得到一个
系统性偏低的覆盖率,而缺的恰好是"只在某种模式下才生效"的分支 ——
那通常是豁免、降级、兼容开关这类让工具闭嘴的分支。
豁免路径是最该被钉住的:它写错了,表现是工具安静,而安静看起来像通过。
这里还有一个值得单独记的设计:REASONS 用消息正则去认领发现。
它看着脆(改一句措辞就不匹配了),但两条断言合起来把两个方向都封住了 ——
改了措辞 → 发现变成 UNREGISTERED → 红;
删了代码 → 登记的原因没响 → 红。
措辞和代码必须同时改,而且自检会说是哪一边先漏。
它第一次跑就抓出两条指向已不存在代码的登记原因。
这条和 用 any() 写的回归测试是假保证——它只要求最容易过的那个夹具通过 是一个家族:那条讲"任意一个夹具过了就算过" 是假保证;这条讲即使改成"每个都要过",枚举的维度选错了照样是假保证。 而 手写的自检清单永远是全绿的——它只包含你刚修过的东西 是同一问题的上一级 —— 手写清单只包含你刚修过的东西。三条的共同点: 覆盖率的分母是人选的,而人会选自己想得到的那一维。
怎么用¶
把"还有哪些 flag 会改变控制流"当成覆盖维度列出来。
一条实用的近似:对每个在代码里出现在 if 条件里的 CLI 参数,
问"有没有一次自检带着它跑"。带不带 --kind / --only / --strict /
--assume 是不同的程序。
输入和参数要在报告里分别记。 上面那张表之所以能一眼看懂, 是因为我把两次跑的差集打出来了。如果只打一个"27/27 通过", 下次有人简化自检、顺手把那个带 flag 的第四次跑删掉,计数会掉到 26, 而 26 和 27 的区别没人记得意味着什么 —— 所以断言里要写期望的具体集合, 不是期望的数量。
警惕只有一个夹具能触发的原因是互斥的信号。 这个项目需要三个 对抗夹具,理由是有些原因天然互斥(一篇论文不可能同时"豁免了 teaser" 和"缺 teaser")。互斥的原因不能靠把夹具写得更脏来覆盖, 只能靠多一个夹具或多一组参数 —— 发现自己在给夹具加互相矛盾的内容时, 应该停下来加文件,不是继续加内容。
出处¶
~/Tools/nonexpert-review:nonexpert_review.py:460(REASONS 的 27 条登记)、
CALIBRATION.md 「地板,在子原因粒度上」一节、
fixtures/tripwire.tex / tripwire_b.tex / tripwire_c.tex;
自检的四次跑在 ~/Tools/skill-backend-check.sh:1755-1776。
上面两次覆盖统计是我自己在 2026-10-11 跑的
(check <fixture> --json,再按 findings[].reason 和 REASONS 求差集),
工具版本 2026-09-25.1。