昨天結尾我說:那 18 張統整卡好到我不安。
但「不安」不是證據。它不能拿去跟任何人講,也不能拿來決定要不要用這份文件。
所以我做了唯一能做的事:把它當成待測物。
這是整件事最卡的地方。
系統可以測——你點下去,它有反應,反應對或不對很清楚。文件呢?文件不會「執行」,它只是一段一段的句子。你要怎麼對一段句子說 FAIL?
所以第一步不是開始查,是先把這份文件拆成一條一條可以被判定的宣稱。
而拆的時候我立刻發現:它們不是同一種東西。
18 張卡上的內容,大致可以分成兩類。
第一類:可查證的宣稱。
XXX_ENABLED 控制」 → 程式裡有沒有這個旗標?SomeClass::someMethod」 → grep 下去,有就是有,沒有就是沒有這一類有一個共同點:它們在外面有一個對應的真實可以比對。 比對的結果是二元的,沒有解釋空間。
第二類:不可查證的宣稱。
這一類不是錯的,也不是廢話——它們往往是最有價值的部分。但它們沒有辦法被否證。你沒有一個地方可以去查「這個設計是不是為了避免誤解」。
我只測第一類。
這是一個刻意的取捨,而不是偷懶:一個無法被否證的宣稱,測它是沒有意義的。 我唯一能做的是「讀起來合不合理」,而那正是 Day 1 那段假規則騙過我的方式。
但這裡要講一句很重要的話,因為它後來反咬我一口:
沒被測到的部分,不等於正確。它只是這次沒有被測到。
我當時知道這句話,但我低估了它。第二類裡面藏著一種錯誤,是我這套方法設計上就抓不到的。
那是 Day 9 的事。
決定了要測什麼,接下來是:跟什麼比?
我用的是本機四個儲存庫的主線分支。
為什麼是程式碼,不是原始的票卡?
因為票卡會互相覆蓋。同一個地方被改過三次,就有三段描述,而且看起來一樣有自信——你拿卡片去驗卡片,只會得到一個循環。
程式碼不一樣。程式碼沒有「三個版本並存」這種事,它只有現在這一版。它是唯一可觀測、而且不會自我矛盾的東西。
(這句話後來變成我整個作法的轉折點。Day 11 會講。)
方法很土,但每一項都是二元判定:
| 宣稱類型 | 查法 | 結果 |
|---|---|---|
| PR 編號 | 去對應儲存庫查這個號碼 | 存在 / 不存在 |
| PR 主旨 | 存在的話,比對它到底做了什麼 | 相符 / 不符 |
| 合併時間 | 看 merge 日期 | 與卡片敘述一致 / 矛盾 |
| 類別、方法、路由、資料庫遷移的名稱 | grep | 找得到 / 找不到 |
| feature flag | 查程式與設定 | 有 / 沒有 |
| 計數宣稱(幾個分頁、幾條路由) | 自己數一次 | 相符 / 不符 |
沒有任何一項需要判斷力。這是刻意的——我要的是一個事後可以重跑、而且換個人重跑會得到同樣結果的檢查。
如果一個檢查需要我「覺得」,那它就不是檢查,是意見。
這裡必須講清楚,否則這篇文章會變成它自己在批評的東西。
而且我要先更正自己一句話。
Day 1 我是這樣寫的:「我把那十八張卡拿去對照程式碼,一項一項查。」
那句話不算假,但它不夠精確,而且在這個系列裡,不夠精確就是不夠好。準確的說法是:
實際去搜尋那些編號、grep 那些符號名、比對合併日期的,是 AI。我做的是下指令。
我知道這聽起來很像一個笑話:用 AI 去驗證 AI 寫的東西,那不是循環嗎?
我認為不是,而理由就在上面那張表。
一個檢查會不會變成循環,關鍵不在執行者是誰,在於這個檢查需不需要判斷力:
換句話說:我把「需要判斷的部分」全部留給自己,把「只需要比對的部分」交出去。
而分工線就是昨天那張可查證 / 不可查證的分類表。它看起來是在決定「測什麼」,實際上它同時也決定了「誰來測」。
這件事我覺得更重要。
整個流程裡有幾個環節是不能外包的:
AI 負責的只有中間那段機械勞動:去搜、去比、把結果貼回來。
我負責的是「什麼叫通過」。
回想 Day 2 那張 32 則留言的卡——我當時也是把東西丟給 AI,結果是它幫我寫 AC、幫我測、幫我判讀,整條流程跑完而沒有人在驗證。
同樣是找 AI 幫忙,差別在哪?
差別不在有沒有用 AI,在於判準是誰定的、結果能不能被重驗。
Day 2 那次,判準是它給的,而我沒有能力重驗。這一次,判準是我定的,而且每一項都可以被重跑——如果你不相信結果,你可以自己再查一次那個編號。
會。這是這套作法真實存在的風險。
它可能搜錯儲存庫、看錯分支,然後回報一個假的「查無此項」——那樣我會得到一個假的錯誤,而不是漏掉一個真的錯誤。
防線有三條,而且都很土:
這三條加起來,就是我敢把執行交出去的全部理由。
我想在這裡停一下,因為這件事我做的時候才意識到,而它大概是這整個系列裡我最喜歡的一個觀察。
平常的 QA 是這樣運作的:
待測物是系統,測試依據是文件。
我拿著規格、票卡、驗收條件,去點那個系統,看它有沒有照文件說的做。文件是尺,系統是被量的東西。
而這一次:
待測物是文件,測試依據是系統。
這次是拿程式碼去量那份文件,看它有沒有照系統實際的樣子寫。
尺和被量的東西,對調了。
而方法完全沒有變——一樣是拆宣稱、定判準、逐項比對、記錄不符。
這件事之所以重要,是因為它說明了一件我原本沒想清楚的事:QA 的技能不是「會測軟體」,是「會驗證一個宣稱」。 待測物是什麼並不重要。
那也是為什麼我覺得這件事該由 QA 來做——不是因為我懂 AI,是因為我習慣不相信輸出。
還有一個決定:我把所有可驗證的宣稱全部查完,沒有抽樣。
抽樣比較省時間,而且統計上完全站得住腳——抽 20% 估出錯誤率,是很正常的做法。
但我要的不是錯誤率。
我要的是「這份文件能不能直接給人用」。
如果我抽查了 20%、發現 3 處錯,那我得到的結論是「大概還有 12 處錯在裡面,只是我不知道在哪」。那份文件我還是不敢給新人。
錯誤率是給決策用的數字;而我要的是一份可以交付的東西。這兩件事需要的證據量不一樣。
先誠實講:沒有。我沒有定過那個數字。
而且我當時就知道,自己不可能做到完美。
因為就算我把每一個 PR 號、每一個符號名都查完,那也只涵蓋得到硬事實。而這批卡片本身還帶著另一種陷阱,而且陷阱不是我設計的——是資料本身就有的:
這兩種東西,沒有一個 grep 抓得到。要判斷「哪一段才是現在有效的」,你得先知道整段歷史。
範圍這麼廣,我幾乎可以確定自己抓不完。
而且說一句實話:連商用的大模型都會給我錯誤資訊,我也沒什麼立場要求自己 100%。
所以我的標準就是四個字:盡力而為。
我知道這在 QA 的語彙裡是很不合格的答案。但我寧願誠實說我沒有定標準——也不要事後才定一個剛好及格的數字。 後者是這一行最常見的自我欺騙:測完再決定什麼叫通過,那個門檻一定會剛好落在你的結果下面一點點。
當時我以為我在做驗收。
現在回頭看,那天真正發生的事情是:蒐集資料。
我查到的那 8 處錯誤,最後沒有變成一份「請修正以下項目」的清單。它們變成了證據——關於「這個做法會在哪裡失敗」的證據。
而那批證據,後來決定了整個知識庫的架構。
(真正拿問題去逼它回答、看它會不會亂牽拖,是一個多禮拜以後的事了。那部分在 Phase 5。)
我後來想到一個比喻,它大概是我對這個專案最準確的描述。
這座圖書館裡的書,就是那些票卡。 有舊的,有新的。
而關鍵是:每一本在它被寫下來的那一天,都是正確的。
它們不是錯的書。沒有人寫錯什麼。只是時間一直在往前走,於是有了新的情況、新的決定、新的觀點——然後又多出一本新書,擺在舊書旁邊。
兩本書都在架上,都是真的,都很有自信。
那要怎麼知道哪一本才是「現在」?
目前為止,方法只有兩個:看出版日期,還有——
我的腦袋。
除了日期以外,判斷哪一本書才是最新版資料的唯一途徑,就是我記得。
而這句話,就是整個專案要解決的問題的全貌:
我是那個唯一的索引。而我會忘記、會漏看、會請假,而且總有一天會離開。
我真心希望現在是《攻殼機動隊》的世界。那樣我就可以去偷一顆愛因斯坦的腦,請它來當這座圖書館的管理員。
前提是它沒有失智。
但現實是,我手上只有一台沒有獨立顯卡的筆電。
所以這座圖書館真正需要的,不是一個更聰明的館員——
是一套不依賴館員記憶的排列方式。
(這個比喻在 Day 11 會回來,而且那時候我會發現:問題不只是書怎麼排,而是我一開始就走錯了房間。)
最後講一件很實際的事。
這件事花掉的時間,比讓 AI 產出那 18 張卡多得多。產出是幾分鐘,驗證是好幾個小時。
那我為什麼不直接用?
因為這份文件的下游是人。它會被拿去當測試依據、被新人當教材、被引用到別的卡片裡。文件跟程式不一樣的地方在於:程式錯了會壞給你看,文件錯了會被相信。
而且錯誤在文件裡是會複製的。一條錯的敘述被引用三次,就變成三個地方都錯,而且後來的人會覺得「三個地方都這樣寫,那應該是對的」。
Day 1 那件事就是這樣發生的——那條錯的敘述沒有停在文件上,它走到了一個新人手上,變成一份假的測試失敗回報。
驗證的成本是一次性的,不驗證的成本是複利的。
明天講結果:8 處錯誤。
包括一張卡上寫著「此問題尚未修復、尚未看到 PR 紀錄」——而那兩個問題都已經修好合併了,合併日期還早於卡片的更新日。
還有三個 PR 編號,在對應的儲存庫裡根本不存在。