論述基礎與限制

本卡從一次書單分類的擴充與五輪審查抽出。那批內容新增了兩篇主題篇與七個書目小節,過程中有三組可數宣稱要跟著更新:書目小節總數、處境相容性判定的三分佈、以及各篇機制詞的數量。三組都更新了,而且更新的人知道計數單位的定義寫在哪裡。

三組裡有兩組錯了,錯法相同:取舊值、加上這次新增的數量、寫回去。總數因此永遠對得上,分項則不然。

限制有三層。第一層是這個數字要由人維護——由內容自動產生的計數不存在這個操作。第二層是這個數字要有分項:只有一個總數的宣稱,加法與重算的結果相同,本卡對它不成立。第三層是下面的實例出自單一批次,它說明這個缺陷長什麼樣、通得過什麼樣的檢查,不說明它多常發生。

推翻方式很具體:拿一組有分項的可數宣稱,用加法更新一次、再逐項重數一次,兩者結果相同,本卡對那一組就不成立。


核心原則

一個由人維護的可數宣稱有三個獨立的失效面,而多數檢查只涵蓋前兩個。 值算錯了,重算一次就知道;沒有人會回頭算,看的是重算的觸發綁在什麼事件上;而第三個是有人回頭算了,動作卻不是重算。

加法與重算在輸入相同時給出同一個總數,所以它們的差別落在各自能發現什麼上,而不在算術上。重算會重新走過每一個被計數的對象,因此順帶檢查了「這一項到底算不算數」;加法不走那一遍,它預設舊值正確、也預設新增的每一項都落在同一格。兩個預設都可能不成立,而加法對兩者都沉默。

總數自洽是這個缺陷的保護色。 分項加起來等於總數時,一份驗算報告寫得出「對得上」,而那句話成立——它只是回答了另一個問題。分項與總數同出一次計算時,它們必然互相對得上,自洽因此不證明任何一項被判定過。


反模式:驗算了總數,就當成分項驗過了

實例裡有一個獨立的驗算環節,而它通過了。

書目小節從六十九個增加到七十六個,處境相容性的三分佈從三十七/六/二十六被更新成四十四/六/二十六——三十七加七。驗算者實際重數了總數:七十六,與宣稱相同,回報「一致」。

第三輪才有人逐節重判分項,發現新增的七個小節裡有一個沒有處境判定(它有的是證據外推的範圍限制,那是另一個維度),正解是四十三/七/二十六。總數不變,兩個分項各錯一。

同一批的第二個實例更直接:某處宣稱的機制詞「共七個」,是舊值五加上這次新增的兩個。而舊值五本身在這一批之前就漏了一組,前兩組實際是六個主題、至少十個詞。加法把一個既有的錯誤原樣接了下去,而重算會撞到它。

第三個實例在段落層:同一批的段落內列舉計數錯了三次(宣告四個面向而列了五個、宣告其他三本而實際四本、宣告四件事而有五節),三次都是加了成員之後只改了句尾的計數而沒有回頭數段落開頭那一個。這一類沒有任何住址登記它們——站級的計數住址清單管的是總數,不管段落裡的列舉。


修法

把更新動作寫成重數,而不是寫成加減。 具體的差別是有沒有走過被計數的對象:更新前先跑一次逐項的枚舉(哪些檔、哪些節、各自落在哪一格),拿枚舉的結果去覆蓋舊值,而不是拿舊值去接新增。成本是一條指令,而它同時檢查了舊值。

驗算要指定驗的是分項還是總數。 要求驗算者附上逐項的判定表(每一個被計數的對象落在哪一格),而不是只附總數。沒有逐項表的「對得上」只證明分項與總數出自同一次計算。

分項的定義要能單獨判定。 實例裡的三分(寫出限制/明說查過而沒有依賴/尚未檢查)在條目層沒有標記,只在彙總層有三個數字,於是「這一節屬於哪一格」每次都要重新判斷一遍,而重新判斷正是加法省掉的那一步。條目層留下標記之後,重數會退化成計數,加法與重算的差距也就消失。

段落內的列舉計數要進同一份住址清單。 站級計數有住址登記而段落內的沒有,是實例裡那三次錯誤的直接原因。

而這一條不要升到工具鏈,理由是量過的。實例的下一步實作了一條 lint 警告:偵測「宣告數量的詞」加上「緊接的一組列舉」,兩者不符就報候選。全站命中 265 個,抽樣全是假陽性;收緊三輪(排除序數與列表項內的宣告、改取緊鄰列舉的那個計數、砍掉清單形態只留同段粗體)之後降到 16 個,逐個看仍有約七成是假陽性。

失敗的原因是綁定關係本身:一段散文裡出現的數量詞多數不在描述緊接的那組列舉(「跨區拓樸三類:mesh vs hub-spoke vs managed global。Kafka……RabbitMQ……」——三類在純文字裡,粗體是產品名;「三篇分析已經指出這個區分:……作用在格式層……作用在框架層」——三篇是文章數,粗體是層級)。宣告與列舉之間有沒有綁定,要讀那句話才知道。

依升級階梯的兩個條件(偵測規則已穩定、誤判可控),第二條不成立就停在生成端。七成假陽性的警告放進 pre-commit 會訓練人忽略警告,那比沒有規則更糟。這一條的防線因此是寫作當下的動作(更新時重數),不是事後的掃描。


判讀徵兆

  • 更新的說明寫成「加了 N 本,所以從 X 改成 X+N」——那句話本身就是徵兆
  • 分項加起來剛好等於總數,而沒有人說得出上一次逐項判定是什麼時候做的
  • 驗算報告只寫「一致」或「對得上」,沒有附逐項的落點
  • 同一個數字有多個住址,而更新的人只改了自己眼前那一個
  • 段落開頭宣告的數量與段落結尾宣告的數量不同(實例裡出現過,而兩處相距十四行)
  • 一個計數的分項在條目層沒有標記,只存在於彙總頁的幾個數字裡

這條原則什麼時候不成立

只有總數而沒有分項的宣稱,加法與重算等價,本卡不適用。

由內容自動產生的計數(模板算出來的、CI 產出的)不經過人的更新動作,這裡討論的操作不存在。

被計數的對象數量大到重數的成本明顯超過分項錯誤的代價時,正確的做法是把計數自動化或降級成範圍,而不是繼續用加法——本卡主張的是加法不該被當成重算,不是主張人一定要手動重數。

快照型的數字(記錄某個時點查到幾門課)不隨後續變動,它不需要更新,也就沒有這個問題。


跟其他抽象層原則的關係

#287 重算的觸發要綁在會改變那個數字的事件上 是直接的上游。那張卡列了兩個失效面(值算錯了、沒有人會回頭算),本卡補的是第三個:觸發正常發生、人也動手了,而動作不是重算。兩張卡共用「內部自洽會蓋住外部失準」這個機制,差別在那裡的數字停在舊值,這裡的數字動了而且總數是對的——後者更難查,因為它有一次看起來成功的更新可以指。

#291 掃描指令壞掉時不會報錯,它報一個數字 是同一個症狀的另一個來源。那裡是量測工具量到別的東西,這裡是量測動作根本沒有執行而由算術代替。兩者的輸出無法區分——都是一個看起來合理的數字——所以判讀要往回看產生它的動作,而不是看數字本身。

#266 可核對的錨要跟它該偵測的狀態連動 給的是指標的選擇判斷標準,本卡處理的是那個指標被更新時發生什麼。一個完全符合 #266 的指標,配上加法式的更新,仍然會在該偵測的狀態出現時給出正確的總數。

#221 檢查規則的作用域要顯式列舉 講未納管目錄的零錯誤與合規目錄的零錯誤訊號相同。本卡的段落內列舉計數是同一個形態:站級計數有住址登記、段落內的沒有,於是後者的錯誤永遠不會被系統性發現。兩者共用的處置也相同——把涵蓋範圍變成可以逐條核對的東西。

#247 多次局部正確的修法會合成缺陷 描述的是多次各自正確的修改合成一個新問題。本卡的實例是它的一個子型:每一次計數更新單獨看都做了該做的事,而它們共用同一個省略(不重數),於是錯誤跨批次累積而不是被下一次更新沖掉。

#293 撰文脈絡以兩種尺度洩漏 與本卡共用同一個機制在不同對象上:完成的訊號來自最容易驗的那一項——這裡是分項加起來等於總數,那裡是掃描指令降到零命中——而那一項不是會出錯的那一項。

#294 非互動環境裡,要人停下來看的關卡表現成當機 的旗標不對稱與本卡的驗算不對稱同型:驗總數與驗分項、問「它會不會做我要的」與問「它還會做什麼」,前者都便宜而且都會通過。

#295 查不到的反證要換一個維度 是同一種自洽假象在查證上的形態:分項加起來剛好等於總數只證明它們出自同一次計算,多個來源一致也只證明它們收到同一組查詢詞。

  • #318 每一格都驗過,不代表由格子導出的那句話驗過了 是本卡的驗證側對應:本卡的失效在更新(內容變了而數字用加的),那裡的失效在驗證(數字沒變而驗的方法碰不到它)。兩者共用「總數自洽會讓錯誤通過」這個機制,而那裡連分項都是對的,錯的只有由分項導出的那一步。