Day 07 · W1 · AI 線 · 難度 ★★☆☆☆
本系列由 AI 協作撰寫。 內容、技術判斷、程式碼由 light-design 數位顧問團隊與 Claude 共同產出,最終由作者驗證後 publish。完整協作模式與把關方式見 Day 01。
Day 4 那個 _MAX_LABEL_LEN = 16 我說留到今天講。
今天不是要再講一次那個漏抓。是要講那個數字是誰決定的。
答案是我。而且不只那一個。
if b_count >= 5 and sem_count == 0:
一句話主軸:開源不是慷慨。是因為那些判斷除了程式碼以外,我沒有寫在任何地方。
隨手從規則檔裡抽出來的:
| rule_id | 程式碼原文 | 那個數字在決定什麼 |
|---|---|---|
HM1130105E |
b_count >= 5 and sem_count == 0 |
幾個 <b> 才算濫用非語意標記 |
AR2410302E |
len(children) >= 5 |
幾個子元素才算一個「訊息日誌」容器 |
FA2410303E |
len(text) > 80 |
超過幾個字就不當作狀態訊息 |
GN2320400E |
len(existing) > 3 and len(text) > 3 |
多短的連結文字不列入比對 |
GN1240301E |
len(out_of_viewport) > len(tab_stops) / 2 |
多少比例的焦點在視窗外算異常 |
GN1330100E |
5 < len(text) < 300 |
什麼長度的文字算「錯誤訊息」 |
沒有任何一條規範文字叫我們寫這些數字。
規範說的是「不應濫用非語意標記」。5 是我們替那句話決定的操作定義。某天下午,看了幾個網站,覺得這個數字差不多。
四個 <b> 的網站不會被判,六個會。那條線在哪,是一個人畫的。
這件事本身不可避免。規範用文字寫,程式要能執行,中間一定得有人把「濫用」翻譯成一個可以比大小的數字。問題不在有沒有這條線,在這條線對不對得上你的網站,以及你看不看得見它。

左邊是程式碼,右邊是規範原文。中間那段落差,就是我拍板的地方。
閾值只是其中一種拍板。規則對頁面結構也做了假設,而每個網站的結構不一樣。
三個實例:
| rule_id | 程式碼裡的假設 | 誰會被誤判 |
|---|---|---|
GN1240102E |
主要內容錨點接受 <main>、role="main"、id="main"/"content"/"maincontent" |
掛在 #app 或 #root 的 SPA |
GN1240100E |
只看 <body> 裡前 8 個 <a> 找 skip link |
頁首有長工具列的站 |
GN1240500E |
導覽認 <nav> 或 role="navigation" |
用其他語意結構做導覽的站 |
第一條特別值得看。一個 React 專案掛在 <div id="root">,內容區塊語意上完全正確,但因為 id 不叫 main 也不叫 content,我們會報它缺少內容錨點。
那不是它的錯,是我當初只想到三個名字。
這一類假設比數字更難察覺。閾值至少是個數字,看到就知道是人挑的;結構假設藏在條件式裡,長得像事實。id="main" 讀起來像標準,其實只是常見寫法之一。
第二條的 [:8] 同時是數字也是結構假設,等於第七個魔術數字。
還有更土的。那條找訊息日誌的規則,是靠 class 名稱猜的:含 log、chat、message-list、notifications 就當候選。然後就踩到了:log 這個字串會中 blog、dialog、logo、login、catalog。所以程式碼裡多了一條排除清單,專門把這些洗掉。
那條排除清單旁邊還附了一句註解說明為什麼。它是一個猜測的補丁,不是規範裡的東西。
想調那個 5,你能做什麼?
規則設定檔 不存在(grep toml / config_file / rcfile 全空)
CLI 能調的 --ignore(整條關掉)· --freego-only · --strict-third-party
閾值狀態 全部寫死在規則檔裡
--ignore HM1130105E —— 整條關掉,連帶失去這條規則所有價值第一條是把嬰兒跟洗澡水一起倒掉。一個網站合理地用了很多 <b>,不代表它不想檢查語意標記,只代表那個 5 對它不對。
寫到這裡我本來想收尾了:「所以你看,開源讓你能改。」
然後我去查了 axe-core 怎麼做的。
axe.configure runtime function
checks[].options 官方明文可傳參數給個別檢查
rules[].enabled 可單條開關
自訂規則 / 覆寫既有規則 都支援
用 axe 的人不需要 fork。想把某個門檻調鬆,寫一段設定就好,原始碼一個字都不用動。pa11y 有設定檔、Lighthouse 有 custom config,都是同一套路。
這不是小事。一旦要 fork,你就得自己維護那份分支:上游改了規則要合併,出新版要重跑一次改動,團隊換人要交接那份 patch。多數團隊評估到這裡就放棄了,改成「這條報出來我們就忽略」,然後那條規則在他們的流程裡實質死掉。能改跟改得起是兩件事。
真正逼你改原始碼的是我們。
所以這不是兩條路,是三種處境:
| 處境 | 代表 | 使用者能做什麼 |
|---|---|---|
| 開源 + 有設定機制 | axe-core、pa11y | 調參數,原始碼不動 |
| 開源 + 沒設定機制 | 我們現在 | 改原始碼,或整條關掉 |
| 閉源 + 沒設定機制 | — | 只能整條關掉 |

我們比最差的好,但離最好的還有一段。那一段是我們自己欠的。
不是慷慨。
是因為那些判斷除了程式碼以外,我沒有寫在任何地方。
這句話可以當場驗證:README 跟文件裡沒有任何一個閾值;跑 a11y-moda rules show HM1130105E,回給你的是 WCAG 準則、等級、主題、來源、適用階段,就是沒有那個 5。
$ a11y-moda rules show HM1130105E
### HM1130105E — 使用語意組件來標記強調的文字或特殊文字
- WCAG SC: 1.3.1
- Level: A
- Topic: structure
- Source: extension
- Scope: scan, lint
那個 5 只存在於一個地方。不打開它,你就不知道那條線畫在哪。
這也是為什麼我不會說「閉源工具不誠實」。閉源工具照樣可以把閾值整理成文件公開,那也是誠實的,只是不用開源做到。我們沒寫文件,所以我們的判斷只有一個出口。
換句話說,開源在我們身上補的是一個自己造成的缺口。這句話講出來不好看,但它比「我們選擇開源因為我們相信透明」準確得多:後者是立場宣示,前者可以查證。
前六天一直在講「我們會犯錯」,卻沒講「所以你要能看見並修正我們的錯」。那正好是開源的定義。
| 前面寫過的 | 沒有原始碼的話 |
|---|---|
| Day 2 一條規則方向寫反 | 那個錯會一直錯,永遠沒人發現 |
| Day 3 import 寫錯規則會靜默消失 | 規則總數少一條,你查不出來 |
Day 4 那個 16 差一個字元就漏抓 |
你不會知道有這個門檻存在 |
| Day 5 「55 個連結」是我們挑的 | 你只能接受,質疑不了 |
| Day 6 對帳分類是我們的判斷 | 沒人能覆核我們有沒有讀錯 |
而且整個系列每一句「你可以自己查」都建立在這件事上。Day 3 那段 commit log 如果沒有原始碼可以覆核,它就只是我單方面說的故事,跟任何一篇心得文沒有分別。
那顧問公司圖什麼?
這個問題我被問過幾次,通常帶著善意的困惑:你們做網站,把檢測自家作品的工具送出去,等於把評分標準交給別人。
一個做網站的公司,把檢測自己作品的工具送出去,第一眼看確實不合理。但這個問題問反了:我們賣的從來不是那把尺,是知道怎麼量、量完怎麼修。 一份掃描報告可以自動產生,判斷哪幾條該先修、哪幾條是誤報、哪幾條要動設計而不是動程式碼,那部分自動化不了,而那才是客戶真正付錢買的東西。尺公開之後,我們少了一個可以拿來收費的黑盒子,但換到兩件事:一是不用在簡報裡形容自己多嚴謹,對方自己打開看;二是外面有人幫我們找錯,而檢測工具的價值幾乎完全等於它錯得夠不夠少。
不過我不想把這篇寫成開源好棒棒。我們只做到一半。
看得到那個 5、改得動那個 5,是地板。不必改原始碼就能調,才是 axe 已經站到的位置。 現在的狀況是:你可以改,但你得 fork 一份自己維護,那個門檻對多數人來說跟不能改沒兩樣。
該做的是規則層級的設定檔,讓人不必動程式碼就能調閾值。這件事還沒做。
我也想清楚了為什麼一直沒做:因為自己用的時候不需要。我知道那個 5 在哪、要改哪一行,於是那個缺口對我不存在。寫工具的人最容易看不見的,就是自己不會踩到的門檻。
順帶一個更小但更尷尬的缺口:rules show 不吐閾值。這個工具的定位之一是讓 AI agent 寫程式前先查規則,但查到的東西裡沒有判定門檻 —— agent 只知道「要用語意標記」,不知道「超過 5 個才會被判」。
W1 到這裡收尾。明天開始 W2,攤開實作:Python 一行都不是我寫的,那我到底在做什麼。