补充: 给"条数登记校验"加锚点(自检 5b)—— ★ 而我第一版锚点自己写成了**空真**

`788d7cc` 把条数校验挪进 `else { … }`(红绿都跑)之后,**它没有自检**:
下一个人完全可以再挪回 `else if` 后面,那时**什么都不会红**
(红文件又收不到条数回执),而缺口**只在文件恰好红时隐形** ——
正是它上次潜伏到 `c523c21` 的原因。⇒ 补自检 5b。

做法(锚点落在**实际发生的比较**上,不许落在源码文本上,§16.3):
在 `else { … }` 里每次比较都 `countCheckRan.add(file)`,
5b 从 `records`(谁真的自报了条数)**独立重算**应当被评估的集合,再要求它被覆盖。
**不读 `reds`、不看那条校验自己的输出** —— 否则就是"读数器自作证"。

★★ 而**我第一版 5b 是空真的**(自捉,如实记):
我原来比的是"凡**条数不符**的文件都必须被记录过" ——
**而这条修复本身就把 harmony-admin 的登记数对齐了 ⇒ 那个集合恒空 ⇒ 断言恒真。**
变异测试当场抓到:把 `countCheckRan.add` 挪回"只绿才走",**5b 一声不响** ——
那一刻我才发现它不是"通过",是"**没有对象**"。

改成比 **"所有自报了条数、且没崩的文件"**(红绿都含):红文件必在其中 ⇒ 非空,
且"红文件被漏记"必被抓。另加**反空转**:集合为空而并非全部 broken ⇒ 自检自己报失效。

变异验证:挪回"只绿才走" ⇒ 5b 报出 6 个红文件名
(cross-client-theme / build-stamp / align-refs / harmony-push / criteria-hygiene / harmony-admin);
基线不报 ✓。修后全套(树内):files=31 ran=31 checks=487 pass=476 fail=11 red=10
broken=0 unreported=0。

`CRITERIA.md` 补两条可复用教训:
① **"拿现有数据试一遍"要试到"数据非空"** —— `∀x∈∅` 的判据看起来和真判据一样绿;
② **修好一件事会同时消灭它自己的测试对象** ⇒ 锚点不许建立在"当前的错误状态"上,
   要建立在**恒在的集合**上("谁自报了条数",而不是"谁条数不符")。
This commit is contained in:
2026-09-19 12:56:19 +08:00
parent 788d7ccb20
commit d3140c213c
2 changed files with 74 additions and 0 deletions

View File

@ -518,11 +518,36 @@ test/harmony-admin.test.mjs: 登记=22 实际=27 exit=1 ★ 红 ⇒ 校验
**变异**:把红的 `harmony-admin` 登记数改成 `99` ⇒ 报 `自报 27 条 < 登记的 99 条` ✓;
改成 `27`(对齐)⇒ **不报条数**、只剩"退出码 1" ✓。
#### 给这条修法加锚点时,**我第一版自己写成了空真**(dsh 自捉,如实记)
修完要加个锚点钉住"校验真被评估过",否则下一个人挪回去**什么都不会红**。
我第一版写的是:**"凡『条数不符』的文件都必须被记录过"** ——
**而这条修复本身就把 `harmony-admin` 的登记数对齐了 ⇒ `mismatched` 恒为空 ⇒ 断言恒真。**
变异测试当场抓到:把 `countCheckRan.add(file)` 挪回"只绿才走"的位置,
**5b 一声不响**(这时我才发现它不是"通过",是"没有对象")。
⇒ 改成比 **"所有自报了条数、且没崩的文件"**(`shouldBeRecorded`,红绿都含):
红文件一定在里面 ⇒ 非空,且"红文件被漏记"必被抓。
另加一条**反空转**:`shouldBeRecorded` 为空而并非全部 broken ⇒ 自检自己报失效。
**变异验证**(挪回"只绿才走"):5b 报出**6 个**红文件名
(`cross-client-theme`、`build-stamp`、`align-refs`、`harmony-push`、`criteria-hygiene`、`harmony-admin`),
基线**不报** ✓。
★ 两条可复用的:
1. **"我拿现有数据试一遍"要试到"数据非空"** —— `∀x∈∅` 的判据**看起来和真判据一样绿**,
而它连"有没有对象"都没问过(本仓的"空真"陷阱,这已是第 N 次,第一次发生在我自己的新判据上)。
2. **修好一件事会同时消灭它自己的测试对象**:我把登记数对齐 ⇒ "条数不符"这个集合就空了。
⇒ 锚点**不许建立在"当前的错误状态"上**,要建立在**恒在的集合**("谁自报了条数")上。
★ **通用规则**:
> **校验写在哪条分支上,决定它保护谁。**
> 凡"出错时要额外检查 X"的守卫,先问:**这条分支真红的时候,它还跑得到吗?**
> —— 把校验放在"一切正常"的那条路上,等于**只在没出事时守着**。
> 而给它加的锚点,**不许以"当前的错误状态"为对象**(那会被同一次修复清空)。
计数必须写在 **`check()` 内部**:写在调用点或靠扫源码,"实现被换空"就看不见了
—— 上面那个反例的 `pass` 会是 0,正是靠这一条才有分辨力。