iT邦幫忙

2026 iThome 鐵人賽

DAY 30
0
Claude AI

盡信 Claude,不如無 Code — 心法與全端實戰系列 第 30 篇

Day 30 三十天結束:一份可以直接抄的起手式清單

  • 分享至 

  • xImage
  •  

Day 1 我說,如果只帶走一句話,我希望是這句:

一份你沒驗過就採信的 code,比沒有那份 code 更危險。

三十天過去,這句話我沒有改,但我對它的理解變了。


後十天的 lab,結果如何

後十天,我在 event-signup-lab 把一個活動報名系統從規格做到後端,以及 web、iOS、Android 三個前端。它不上架,所以沒有「多少人用了它」這種成績單;我最想讓人記住的是兩個數字:AI 在七條題目上踩穿 0 條;我自己的檢查壞了 19 次。(「踩穿」指違反了定好的規則、而且沒有任何檢查擋下。)

規則 AI 第一版(只拿到需求的乾淨 session) 裁判
金額精度(25) ✅ 整數、沒有浮點;/ 100 的寫法被靜態檢查擋下;單位選了元(prompt 沒給,是規格空白) 零浮點靜態檢查 + 測試向量
折扣順序(25) ✅ 空白時選了疊加;我改成擇優後,約兩成的隨機輸入算出不同金額 —— 定義換了,不是它錯 測試向量 + fuzz
價格快照(22、25) ✅ schema 自己設計了下單當下的單價欄位 schema 斷言 + 改價測試
超賣、座位唯一(26) 🟡 batch + CHECK、唯一索引都對;只差一條錯誤優先順序:它交卷時那條還沒寫成測試,我補上之後它才紅 閘門重跑 5 次 + 真實多連線
保留 TTL 三方競態(26) ✅ 到期那一刻,原持有人送出確認、別人搶位、清掃排程同時發生;它把釋放和搶位放在同一個 batch 裡,原持有人一律輸 閘門讓三件事同時放行,重跑 5 次(單一 process;真實多連線沒跑)
JWT 邊界(24) 🟡 六個測試過四個,紅的兩個是時鐘從哪來 六個測試 + 簽章比對

七條題目沒有一條踩穿,紅的幾次也都不是它的判斷錯 —— 併發那次是那條優先順序我沒給它,JWT 那兩次是時鐘從哪來;反覆出事的是硬規則「時間是參數」—— 三份 AI 版在 SQL、JWT、參數預設值裡各藏了一個時鐘,都能跑,但「剛好等於邊界」的那一刻測不到。另一個失準的是工期:我排 9/21 全通、spec-kit 排 10/2,實際是 9/27 —— 9/17 到 9/26 一個 commit 都沒有,9/27 一天 119 個,兩份排程都假設寫程式的是人。Cloudflare 的用量都在免費額度內:流量全是我自己打的,用量比例最高的一項也只到 1.71%。草稿裡原本還寫「N+1 會讓 rows read 翻十倍」,實跑之後 N+1 反而少讀(N+1 讀 52 列、JOIN 讀 60 列),貴的是查詢次數(N+1 查 27 次、JOIN 只要 1 次)—— 結論還是寫成一次 JOIN,但理由換了。

我賭錯的那件事也在這裡。我原本把這七條當成「AI 的失誤區」:埋好陷阱,等它踩。開賽前的前置實驗四個陷阱它四個都沒踩(devlog/prior-experiments/),這一輪也一樣 —— 缺陷大多不在實作,在規格的空白處:單位、優先順序、時鐘從哪來,沒說的它會替你決定。而最不可靠的是我自己的檢查:19 次裡,有抓不到的(工具不存在而全綠)、抓太多的(CI 逾時設太短),也有我自己的腳本和 UI 測試印出假綠燈。我拿一套沒驗過的檢查去驗 AI —— 而這正是我整個系列在批評的事。

一份可以直接抄的起手式清單

如果你要開一個新專案、而且打算讓 AI 寫大部分的 code,這是我三十天之後會照做的順序。

動手之前

  1. 寫 CLAUDE.md,而且只寫規定句,不寫描述句。 描述句 AI 打開檔案就知道;規定句沒有任何檔案能告訴它。
  2. 寫非目標清單,而且讓它比功能清單長。 AI 最擅長的是擴張範圍,而這是最直接擋得住它的東西。
  3. 把規格寫成可判定的句子,然後讓別人來挖空白。「使用者可以報名」沒有裁判資格;「只有主辦能改名額,測試證明越權被拒」有。而寫完之後找一個會問問題的工具過一遍 —— 我自己覺得寫完整的規格,spec-kit 挖出 21 個空白,每一個都是 AI 會替我決定的地方。規格改了之後再讓它對一次程式,它又抓到我自己漏掉的三個併發測試。

開始寫之前

  1. 先部署一個空殼。 部署是最容易被拖到最後然後爆炸的事。
  2. 權限從最小開始開,看不懂就截圖問 AI —— 截圖前先遮掉帳號等敏感資訊。 部署那天 wrangler login 跳出授權頁,一次要 28 項權限 —— DNS、Billing、Cache、AI、Queues 全包。老實說,那一整頁權限名稱我大半看不懂,一路按 Authorize 是最省事的。我做的是把授權頁截圖丟給 Claude,只問一句「幫我看一下,只開最小權限就好」。

https://ithelp.ithome.com.tw/upload/images/20261014/20103790aYW56AlFHU.png

它叫我先按 Cancel,給了一行帶 --scopes 的登入指令,並逐項說明每個權限拿來做什麼。但它第一版的指令是錯的:多放了一個 offline_access,wrangler 直接回 Invalid authentication scope。我把錯誤畫面再截給它,它才改口:那一項是授權頁上必要的「Background Access」,wrangler 會自己加,不能手動指定。第二次,授權頁剩 6 項,每一項都答得出為什麼需要。

https://ithelp.ithome.com.tw/upload/images/20261014/20103790aQcg4FUvNb.png

這件事我覺得比任何一條規則都值得抄:預設值不是你的決定。 最小權限原則聽起來像資安團隊的口號,落到個人開發者身上其實是一個很具體的動作 —— 每一項權限都要答得出「為什麼需要」,答不出來就不開,之後真的用到再補。看不懂不是理由,AI 很擅長把一頁權限清單翻成人話;但它也會錯,所以最後把關的是平台的錯誤訊息和你自己的確認,不是它的第一個答案。權限的人工關卡在 Xcode 那邊也有:用 Xcode 的 MCP 時,每開一個新的 mcpbridge process,Xcode 就跳一次「允許存取」,要你自己按。

  1. 契約先寫,實作後補。 事後補的契約,測的是實作自己(Day 23)。
  2. 關鍵規則自己先寫下來並 commit。 商業邏輯、測試向量、衝突時誰贏 —— 這些是人的工作,程式可以交給 AI。先定規則、再讓 AI 寫,順序不能反過來,而且要留得下證據。

寫的時候

  1. 時間一律從一個入口注入。 不是為了乾淨,是為了「剛好等於邊界」那一格測得到 —— 線上實測打不到那一毫秒(Day 26),只有注入的時間打得到。檢查要掃整個專案,不只 domain:SQL、路由、函式庫、參數預設值都會藏時鐘。
  2. 併發交給資料庫拒絕,不要在應用層先查再寫。 在沒有 FOR UPDATE 的資料庫上,讓唯一索引和 CHECK 拋錯,整批回滾。
  3. 複合條件的測試分開寫。 合成一個,AI 只做一半也會綠。
  4. 規則沒寫成測試,就等於沒定。 寫在規格裡的優先順序,在變成測試之前,沒有任何東西會替它紅(Day 26)。
  5. 給 AI 參考實作之前,先把它修好。 參考實作會替你填規格沒寫的空白 —— 做得好的、沒做好的,都可能一起抄過去(Day 28)。

驗收的時候

  1. 每一支檢查都要先證明自己抓得到,也證明自己不亂抓。 一支從沒用故意弄壞的案例證明過自己抓得到的檢查,跟沒有檢查是同一件事;一支會對正確程式碼亮紅燈的檢查,最後一定會被繞過去。
  2. 分開記「跑過的」與「沒跑過的」,而且沒跑過的不准寫成結果。
  3. 不穩定的重現等於沒有重現。 併發測試要能重跑多次、結果一致(我用五次)。
  4. 想到一個理由之後,跑一次。 上面那句「N+1 會讓讀取翻十倍」聽起來完全合理,跑一次就不成立了 —— 結論還是對的,但理由是錯的 —— 這種錯最難抓,因為結論看起來沒問題。
  5. 讓 AI 驗收時,把期望值寫進任務,再寫進測試。 它只驗你說了的東西:沒說金額該是多少,它就把畫面上的數字當事實(Day 29)。它抓到的那幾次,也只寫在 log 裡 —— 要一直紅,得變成斷言。

這三十天用到的主要工具

前 20 天備工具,後 10 天拿來用。照用途整理:

用途 工具 篇
寫程式、跑實驗 Claude Code(日常的互動 session;實驗用 claude -p 開乾淨 session) 全系列
讀陌生的 repo graphify(把 repo 畫成知識圖) 2–4
把規格問完整 Plan Mode、/grill-me、OpenSpec、spec-kit 7、8、20、21–26
把規則留下來 CLAUDE.md、slash command 與 skill、hook 6、11–12、17
接外部工具 MCP(SQLite、Xcode) 14–15、27、29
隔離 空目錄、換目錄、VM、Docker 容器 4、16、28–29
地端模型 LM Studio + Pi 19、29
後端 Cloudflare Workers + Hono + D1、wrangler 21–30
契約與前端 OpenAPI 3.1;web(Vite)、iOS(SwiftUI + swift-openapi-generator)、Android(Jetpack Compose + OpenAPI Generator) 23、27、28
測試 Vitest、XCUITest、Compose UI 測試、Playwright 9、22–29
驗收與稽核 自己寫的靜態檢查(check-*.sh + self-test.sh)、Stryker 突變測試、iOS 與 Android 的無障礙稽核、Webwright 走查 19、22–29
最後一道門 GitHub Actions 27

工具換得掉,換不掉的是起手式清單裡的順序:先定規則、再讓 AI 寫、最後用會紅的東西驗。

這三十天沒有做到的事

照 docs/verified.md 的 ❌ 欄,加上實作和寫文章時發現的:

  • 還沒在真實環境驗過。 web 走查、iOS 與 Android 的 UI 測試都沒進 CI,iOS 和 Android 也只在模擬器上跑過、沒上過實機;真實多連線下「過期那一刻」的競態也沒跑。
  • iOS 修好的無障礙問題沒合回 ios/。 Day 28 的參考實驗,AI 照抄的就是沒修的那一版。
  • 已知的取捨留著:登入以外沒有限速、email 可以被列舉、token 存在瀏覽器的 localStorage。

開工前我把前 19 天備的工具分成天天用、偶爾、不帶三堆,猜錯的有兩個方向:排進「天天用」的 Claude Code permissions 設定、hook、/commit 都沒用上 —— 真正擋錯的是寫進 repo 的測試和靜態檢查;排在「偶爾」的隔離環境,卻是整個後半的地基。

參賽心得

這次參加鐵人賽,規劃階段只想了簡單的主題和方向:前 20 天寫 Claude 的應用與心法,後 10 天安排一個全端的實作。規劃時,AI 的創造力很強,主題常常無限發散;它也常在文章裡自創一些奇怪的詞和口訣。所以後來統整時,前前後後改了不下五次主題。寫系列文跟做專案不一樣 —— 專案的需求通常有方向、有明確的目標,系列文的方向得自己先定下來。這是這次學到很寶貴的一課。

AI 的優點也很明顯。只要定好方向、想到可以做實驗的點,它的動手能力很強,能很快做完一個又一個 lab;有想測的方向,也能很快跑完,而且動手之前,就能先估出要花多少成本(token、時間)。以前要手工做的部分,真的省下了大量時間。

另外,AI 整理文件的能力很好,但你得先有自己的想法和規劃,不然它會選一個比較通用的寫法,結果就可能跟你當初設想的不一樣。先規劃、再實作,通常是比較好的做法。

審稿也可以交給 AI。現在很流行讓不同的角色、甚至不同的 agent 交叉審稿 —— 例如 Claude 開兩個角色,一個寫、一個挑;或是 Claude 寫、Codex 審。不同的角色、不同的模型看的角度不一樣,交叉看比較容易抓到漏掉的地方;但不管是誰審,建議都要逐條判斷,跟實際跑過的結果衝突時,以跑過的為準,才不會被幻覺帶著走。

最後一點:AI 寫文章很快,內容又多又完整,所以取捨反而變得重要 —— 適當地刪,也是很好的做法。


總結

三十天前那句話,現在我會加一個後半句:

一份你沒驗過就採信的 code,比沒有那份 code 更危險。
而一套你沒驗過就拿去驗別人的檢查,比沒有那套檢查更危險。

因為前者你至少還會提防,後者會讓你安心。

AI 可以讓你很快做出一個作品,也可能讓你對一件事開始感興趣。但軟體工程裡還有很多東西得靠自己補,資安是最明顯的一塊。上面「沒有做到的事」裡的「登入以外沒有限速」「email 可以被列舉」「token 存在 localStorage」,要看得出它們為什麼是取捨、什麼時候不能再取捨,靠的是自己的底子。

作品可以跟 AI 一起做完,學習不行。該補的基礎知識和技能,最後還是得自己一樣一樣補上。

謝謝一路看到第 30 天的你。這個系列的程式碼、測試和每一次實驗的原始紀錄都在 event-signup-lab,大部分的測試和實驗照著 README 都能重跑;起手式那份清單,也歡迎直接拿去用 —— 用的時候記得,連這份清單也先驗過再信。

這三十天,AI 在七條題目上一條都沒踩穿;我自己的檢查壞了 19 次。盡信 Claude,不如無 Code —— 而盡信自己的檢查,也一樣。


參考資料


上一篇
Day 29 讓 AI 走查網頁:五個檢查點全勾,金額錯了十倍
系列文
盡信 Claude,不如無 Code — 心法與全端實戰 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言