iT邦幫忙

2026 iThome 鐵人賽

DAY 25
0
Modern Web

前端不寫 Python,照樣 ship 一把網頁無障礙 CLI系列 第 25

Day 25:我讓 AI 先查規範再寫,差別只在它查到的那幾條

  • 分享至 

  • xImage
  •  

Day 25 · W4 · AI 線 · 難度 ★★★☆☆`

本系列由 AI 協作撰寫。 內容、技術判斷、程式碼由 light-design 數位顧問團隊與 Claude 共同產出,最終由作者驗證後 publish。完整協作模式與把關方式見 Day 01

Day 20 那份給 agent 的說明書,第一句話是:模型不知道這些規則的內容,必須查。

那句話聽起來很合理。但差多少?我原本以為會差很多,查過的版本應該乾淨得多。結果跑完,兩邊的 lint 結果幾乎一樣,差別藏在另一個地方。

Day 23 比的是同一個站修前跟修後,今天比的是同一個需求查前跟查後。量的不是「有沒有修好」,是「有沒有知道」。

一句話主軸:先查再寫有差,但差別只在它查到的那幾條。查的範圍決定改善的範圍。

實驗怎麼設計

需求固定一句話:做一個登入表單,單一 HTML 檔,電子郵件、密碼、記住我、送出,要有即時錯誤提示,繁體中文。需求裡不提無障礙,不提任何碼號。

兩個條件,各寫三份:

實驗設計圖:同一句需求分成兩條路。A 路直接交給乾淨的 Claude 子代理寫,三份;B 路先把 rules search form、input、label 三次查詢的輸出(41 列、28 個檢測碼)放進去再寫,三份。六份檔各跑 lint 50 條、開瀏覽器的 scan 146 條、原始碼對照,比命中的碼號集合

唯一的變數是生成前有沒有查。模型、參數、需求敘述全部相同。

寫的人是乾淨的 Claude 子代理,每一份都是新的對話,沒有這個專案的任何記憶。A 只拿到需求;B 多拿到一份表,是工具的 rules search 對 form、input、label 三個詞的輸出,41 列、28 個不同的檢測碼。skill 裡規定 agent 寫表單前該做的就是這三次查詢,所以 B 模擬的是它照規矩走的樣子。

每組寫三份,不是為了平均,是為了看抽樣的晃動有多大。子代理的取樣參數我控制不了,同一個提示跑三次本來就會長得不一樣;如果三份之間的差異跟 A、B 之間的差異一樣大,那 A、B 的差就不算數。

量尺有三把。lint 讀原始碼,只管 50 條;scan 開瀏覽器跑 146 條,不帶模型;最後我自己把六份原始碼攤開對照。先講誠實條款:我們拿自己的工具評自己的 skill,N 是 3,lint 只涵蓋靜態可驗的那一半。這三句要放在數字前面,不是註腳。

A 的樣子:不慘

我原本以為沒查表的版本會掉一堆基本錯誤。結果六份原始碼攤開,A 的三份全部有:autocomplete、每個欄位對應的 labelaria-describedby 接到錯誤訊息、aria-liverole="alert"aria-invalidrequired。錯誤摘要區有標題、能聚焦。

通用的 a11y 常識,模型本來就有。這一半不需要查。A1 的錯誤摘要長這樣,標題裡有字,數字用 JS 換:

<div id="error-summary" class="summary" role="alert" tabindex="-1">
  <h2>表單有 <span id="error-count">0</span> 個錯誤,請修正後再送出</h2>

lint 跑出來,A 三份各 1 個 fail,同一條:頁面頂端沒有跳到主要內容的連結。另外 1 到 2 個 info,麵包屑跟錯誤訊息的 class 缺 live region 標記。

B 改變的地方

B 的三份多了幾樣東西,每一樣都能在那份表上找到對應的那一列:欄位包進 fieldset,加了 legend;每個欄位多接一個格式範例的說明;B1 在頁面頂端加了跳到主要內容的連結。表上有的,它做了。

然後 lint 報了一個 A 沒有的錯:

<h2 id="error-summary-title"></h2>

B1 跟 B3 都有這一行。它是錯誤摘要區的標題,等到有錯誤時才由 JS 填字,平常是空的。lint 看到的是一個沒有內容的標題,螢幕報讀軟體會唸出一個空標題。A 的寫法是把「表單有 N 個錯誤」的文字直接放在標題裡,數字用 JS 換,標題本身從來不空。

照表寫,多了結構,也多了一種新錯。表上寫的是「提供錯誤摘要」,沒寫「摘要沒東西時標題不能空著」。

B1 那條跳到主要內容的連結也是一半:開瀏覽器按 Enter,焦點沒有過去,因為目標元素沒有 tabindex="-1"。它照表加了連結,表沒告訴它連結要能把焦點帶過去。

還有一個反方向的變化:A 三份都同時寫了 requiredaria-required="true",B 三份全部只留 required。表上那條講的是用 HTML 5.2 的原生屬性,它照做,把重複的 ARIA 拿掉了。這不是錯,但它顯示查表會改變的不只是加東西,也會減東西。

差在哪:兩邊都缺的那些

把 scan 的結果攤開,A 三份的 fail 是 7、6、6,B 三份是 6、6、6。差 0 到 1。

六份表單的比對結果表:lint 那欄,A 三份各 1 fail(缺跳到主要內容連結),B1 1 fail(空標題)、B2 1 fail(缺連結)、B3 2 fail(缺連結加空標題);scan 那欄 A 7、6、6,B 6、6、6;兩邊都缺的五項:跳到主要內容連結、nav 或站內導覽、accesskey 快捷鍵、記住我的勾選框 18 到 22px 不到 24、AAA 對比 6.46 到 6.99 差一點到 7.0;只有 B 多做的三項:fieldset 加 legend、格式範例說明、B1 的跳頁連結;只有 B 多錯的兩項:空標題、跳頁連結焦點沒過去

兩邊都缺的五項,沒有一項在 B 拿到的那份表上。

兩邊都缺的是同一批:跳到主要內容的連結、頁面上沒有 nav 或站內導覽、沒有 accesskey 快捷鍵、記住我的勾選框 18 到 22px 不到 24、AAA 對比 6.46 到 6.99 差一點到 7.0。

這五項有一個共同點:全是頁面級的東西,不是表單的東西。B 查的是 form、input、label,那份表裡沒有跳頁連結、沒有 accesskey、沒有 7:1。它沒查到,所以沒做。A 沒查,也沒做。兩邊一樣。

這就是「差別只在它查到的那幾條」的意思。查表有用,用處的邊界就是查詢的邊界。

lint 幾乎看不出差別,還有另一個原因:B 拿到的 28 個碼裡,lint 管得到的只有 7 個。查表改變的大多是 lint 讀原始碼讀不出來的東西,要開瀏覽器或人看才看得到。所以「兩份都拿去 lint」這一關幾乎是平手,真正的差別要到 scan 跟原始碼對照才露出來。

為什麼是那幾條

再看那五項,有兩項是台灣碼表特有的判定。accesskey 快捷鍵是標章的慣例要求,WCAG 條文裡沒有這個字,工具裡那條規則也是為台灣標章另外加的;AAA 對比 7:1 在 WCAG 是 AAA 等級,模型記得的是 4.5:1,所以六份的灰色說明文字全落在 6.46 到 6.99,剛好過 AA、剛好不過 AAA。這兩項不查,模型憑常識永遠寫不出來。

另外三項是通用的,跳頁連結、導覽、24px。模型知道它們存在,但寫一個「登入表單」的時候不會主動放,因為需求沒提頁面。

Day 05 的三層模型放在這裡剛好:WCAG 條文模型大致記得,碼表的實作要求它不記得,工具的判定門檻它更不知道。查表補的是後兩層,但只補查到的那一段。

代價

先查再寫不是免費的。六份的 token 用量:A 三份平均 48,600,B 三份平均 62,500,多 29%,多的是那份 3,700 字的表跟寫出來的額外結構。查表本身是本機查,零 token;貴的是把查到的東西放進上下文。寫出來的檔也跟著長:A 三份 9 到 12 KB,B 三份 13 到 17 KB,多的是 fieldset、說明文字跟錯誤摘要那些結構。

所以查什麼要挑。表單就查表單,不要把 146 條全塞進去。這也是 skill 把查詢設計成關鍵字搜尋、而不是一次倒整份碼表的原因。

極限

這個實驗量到的東西有邊界,攤開來:我們評自己的 skill;N 是 3,每組差 1 個 fail 就可能是抽樣;lint 只 50 條;scan 沒開模型,所以要語意判斷的那些條沒跑;六份全用 novalidate,探針不按送出鍵,Day 19 那條在六份全是 caveat。

還有一個小地方:同一個空標題,lint 報的碼號是 HM1130100C,scan 報的是 GN2240600EDay 09 講過兩套碼號的由來,這次撞到了實例。

差距比我以為的小,這件事照樣寫。它沒有推翻「必須查」那句話,它把那句話收窄了:查是必要的,查什麼決定你得到什麼。要讓差距變大,下一次該查的不是更多表單規則,是跳頁連結、導覽、快捷鍵這些頁面級的詞,讓表把那五項也蓋進去。那是另一次實驗。

今天的重點

  • 通用 a11y 常識模型本來就有:六份全有 autocomplete、label、aria-describedby、aria-live,A 不慘。
  • 查表改變的只有查到的那幾條:fieldset、格式說明、跳頁連結,全在那份表上;兩邊都缺的五項全不在表上。
  • 照表寫會多一種新錯:空標題佔位,表沒說標題不能空。
  • 碼表特有的判定不查寫不出來:accesskey、AAA 7:1,模型記得的是 4.5。
  • 查的範圍決定改善的範圍,多 29% token 是代價,所以查什麼要挑。

明天 Day 26:五個實驗跑完,回頭數自己。每一件 AI 沒做到的事,都是我以為它會自己做的。


上一篇
Day 24:把瀏覽器縮到 320px,我的網站有幾頁會橫著捲
下一篇
Day 26:每一件 AI 沒做到的事,都是我以為它會自己做的
系列文
前端不寫 Python,照樣 ship 一把網頁無障礙 CLI26
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言