iT邦幫忙

2026 iThome 鐵人賽

DAY 21
0
Claude AI

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

Day 21 開工:規格早就寫好了,它還是標出 21 個空白

  • 分享至 

  • xImage
  •  

昨天讓 OpenSpec 拆了一份舊需求。今天真正開工,要的東西其實都在了:規格、15 條非目標、五條硬規則和它們的裁判,開賽前就寫好在 repo 裡。缺的只有一樣 —— 這十天怎麼排。

我先排了一張表(8 月下旬排的,在工作區裡)。今天讓另一個工具也排一張,然後拿兩張對。工具是 GitHub 的 spec-kit(v1.0.6,9/10 才發),跟昨天的 OpenSpec 是兩種東西:OpenSpec 管一個 change;spec-kit 的 constitution 管整個專案,specify、plan、tasks 原本以一個 feature 為單位。這次把它拿來對整個活動報名系統規劃一輪。它的正式流程是 constitution → specify → plan → tasks → implement,clarify 是可選的一步;故意把它加進來,實際跑五步:constitution → specify → clarify → plan → tasks。後面的步驟都吃前面的產出,而且 plan 那步會拿 constitution 逐條檢查設計。昨天說「一個人三十天的新專案不用 OpenSpec」,講的是 change 級的記錄成本;今天要的是專案級的一張工作清單,正好是另一件事。

規則只有一條:規格不准擴張。它只能把我寫的東西變成它的格式,不能替我加。遇到空白,不准自己填 —— 標出來問我,由我決定。


五步,41 輪,原始規格一個字沒改

步驟 輸入 產出
constitution CLAUDE.md 五條硬規則、15 條非目標、「做完」定義 —— 照抄,不准新增 一份 constitution v1.0.0。想加的規則它列在最後「建議新增(未採納)」
specify docs/spec.md + docs/non-goals.md,一字不擴張,模糊處全部標出來、不准自己決定 364 行、52 條功能需求(依 endpoint 分組,每組從新的十位數起編,所以號碼排到 FR-081) —— 和 21 個 [NEEDS CLARIFICATION]
clarify 21 條的答案寫成一份檔一次餵進去,原則是「市場上最常見的做法」 標記清零,外加一個答案之間的矛盾、兩處與原文的衝突
plan 技術棧鎖死(Workers + Hono + D1 + Pages + SwiftUI + 一份 OpenAPI),五條規則逐條 gate 五條都過,但四個 ⚠️;一份 OpenAPI 契約(18 條、21 個 schema)
tasks 每個 task 附建議日期:9/14 開工、10/13 結束、一人全端、每天要發一篇 60 個 task、8 個 phase,和一張日期 × 里程碑總表

全程 docs/ 一個檔都沒動 —— 每一步都 git status 核過。它想回寫的內容另外寫成一份 spec-writeback.md,要不要動由我自己決定。

21 個空白,在我自己寫的規格裡

先講這 21 個怎麼來的。spec-kit 的 specify 預設最多只留 3 個 [NEEDS CLARIFICATION],其餘要它自己做合理推測 —— 工具的預設是替你猜。我反過來要求它一個都不准猜、全部標出來,結果不是 3 個,是 21 個。

下一步的 clarify 也有上限:它的 command 原文寫著「Maximum of 5 total questions across the whole session」—— 一個 session 最多問 5 題。所以我沒走它的問答,直接把 21 條的答案寫成一份檔餵進去,要它逐條清掉標記。

昨天客戶的 SPEC 被問了七題,今天我自己的規格被標了 21 處。分類:

類 條數 例子
範圍 4 web 做哪些頁?iOS「骨架」有哪些畫面?
資料模型 5 票種名額、活動名額、100 個座位三者什麼關係?hold 綁不綁票種?
流程 5 改票價後,還沒確認的 hold 用哪個價?取消訂單座位釋不釋放?
安全 5 token 效期?refresh 重放偵測後撤多少?QR 裡放什麼?
授權 1 「主辦」是建活動的那個 staff,還是任何 staff?
細節 1 十分鐘「預設」可不可以覆寫?

每一條讀了都覺得「這我當然知道」—— 然後發現規格裡真的沒寫。這跟 Day 7、8 是同一件事:規格先行擋的是範圍擴張,擋不了空白。空白要靠有人來問,而人不會問自己。

所以「規格不准擴張」要講精確:AI 不得自己填空;但它問了、我答了,空白就變成我的決定。 前者是擴張;後者是把原本的空白補成規格。

一口氣答了 21 條。答完它做了一件我沒料到的事:檢查我的答案之間合不合。C3 我說「一個 hold 綁座位加票種」,C9 說「早鳥折扣套在整筆小計上」—— 它指出如果一個 hold 可以混票種、各票種早鳥百分比不同,就沒有單一百分比可以套在小計上;它先採「一個 hold 一個票種」寫進假設,並標出來要我確認。另外兩處是答案跟原文打架:原文狀態機有 cancelled,今天說不做;原文只寫確認後金額不變,今天多說了「未確認的 hold 以新價計」。三處回頭看,都對。

五條規則逐條 gate:沒有違規,但四處要我先拍板

plan 那步最值錢的是 constitution gate。五條硬規則它逐條檢查設計,結論是「無違反」—— 但它自己在四個地方標了 ⚠️:沒有違規,但值得人介入的風險或未決設計。它替我把還沒決定的地方攤了出來:

  1. 規則 III vs D1 的 batch()。規則要「條件寫在 WHERE,看 changes」,但 D1 的 batch 只在拋錯時回滾 —— WHERE 沒命中不是語句失敗,changes = 0 不會觸發回滾。所以名額不足時要讓整批座位一起失敗,真正做事的是 CHECK (remaining >= 0),WHERE 只是不讓正常路徑撞到 CHECK。它列了三個候選、沒選 —— 因為那正是 Day 27 要自己定的規則。
  2. 規則 V 對 hold 表頭的限制。裁判擋所有 UPDATE … SET *_cents,所以 hold 不能先建一列 orders 再回填金額;訂單列與金額必須在確認那次一起 INSERT。表怎麼拆,留給明天。
  3. 規則 I 掃不到 web/。check-money.sh 只掃 src/,web 端的金額顯示沒有裁判;它提議集中到一個檔、擴掃描範圍、補 self-test 探針。Swift 那邊沒有裁判,只能在 verified.md 記 ❌。
  4. src/lib/ 是偷加的目錄。CLAUDE.md 只列了三層,它要新增第四層放 JWT、HMAC、SQL,所以要求先補 CLAUDE.md —— 不補等於違反「不要主動加東西」。

第一條讓我確定 Day 27 的題目是對的;第四條則說明 constitution 真的有在讀。

另外,它讀 repo 時還抓到兩個已經過時的地方:CLAUDE.md 規則 IV 寫「常數時間比對」、docs/spec.md 寫 crypto.subtle.verify,兩邊說法不一致;規則 V 註記「另一半還沒有自動檢查」,但 tests/schema.test.js 第 ⑥ 條從 9/10 起就會為它變紅。

還有一件它做對的事:repo 裡的 docs/EXPERIMENT-PROTOCOL.md 寫了四個「作者手寫、AI 不得先出版本」的實驗對象(schema、金額、時間、併發)。它讀到了,9 個 task 被標成 🖐(8 個實驗、1 個是我自己回寫規格),並註明「本 session 看過考題,不能扮演乾淨 session」。

它排的,跟我排的

這是今天的主線。它的 60 個 task 每一個有建議日期,文末一張總表;我的施工表是 8 月下旬排的。兩張表攤開來對:

里程碑 我排 它排 差
JWT + 註冊登入 9/18–9/20 9/18–9/20 0
17 條 endpoint 全通(當時規劃 17 條) 9/21 10/2 +11 天
schema 之外的三個實驗(金額/時間/併發) 9/22–9/24,一天一個 9/24–9/30,作者版與 AI 版各占一天 +4 天
web 前端 9/25–9/28 10/4–10/6 +9
web 上線 9/29 10/10 +11
iOS 骨架 9/30–10/2 10/7–10/9 +7
緩衝 無 10/11–10/13 —

差 11 天,不是它悲觀,是假設不同:我按整天排,實驗一天一個;它按「每天半天」排(我告訴它每天要發一篇文),而且堅持紅測試先行、每個實驗作者版與 AI 版分開兩天。這三個假設,回頭看都是它比較保守、也比較完整。

但它有一件事不知道,因為我沒告訴它:文章的發文日不等工程。Day 26 要在一個真的跑在線上的系統上等一個開賣時刻,Day 30 的額度表要有東西被打過 —— 那兩篇分別在 10/10 和 10/14 發,素材要在那之前存在。它的排法把 web 上線推到 10/10,到發文那天,素材還沒準備好。規劃工具最佳化的是你給它的目標;你沒給的,它不會替你守。

所以最後的決定:phase 順序用它的,日期用我的。兩張表誰排得準,Day 30 對帳。

第一天不寫功能,先讓它上線

排工是 9/14 做的,真正動手是 9/27。開工第一件事不是寫功能,而是讓一個什麼都不做的 URL 出現在網路上。

理由很實際:部署是整個專案裡最容易被拖到最後、然後在最後一天爆炸的事。第一天就把它做完,後面每一次改動都只是往一條已經通的路上加東西。

npx wrangler d1 execute signup --remote --file schema.sql   # 遠端建表:8 張
npx wrangler deploy
curl -i https://event-signup.<你的子網域>.workers.dev/health
# HTTP/2 200
# x-server-now: 1790479798788
# {"ok":true,"server_now":1790479798788}

真的做下去,最先卡住我的不是程式碼,是權限:

  • wrangler login 預設要 28 項權限 —— DNS、Billing、AI、Queues 全包。這個專案只需要 Workers Scripts、D1、Pages,所以改用 --scopes 指定:

    npx wrangler login --use-keyring --scopes account:read user:read workers_scripts:write d1:write pages:write
    

    授權頁從 28 項變成 6 項。--use-keyring 讓 token 進鑰匙圈,不留明文檔。

  • offline_access 不能自己指定 —— 寫進 --scopes 會直接報 Invalid authentication scope。它是授權頁上那個必要的「Background Access」,wrangler 自己會加。

  • wrangler whoami 會警告缺 workers:write,但部署、cron、secret 都用不到它 —— 警告不等於需要。

  • seed 不上線。 本機的種子帳號密碼是 password123,放到公開網址上等於任何人都能用 staff 身分登入。線上的資料另外建。

為什麼是 Cloudflare

三個免費方案我都用過。這次選型的判準只有一條:以這個專案 30 天的預估用量碰不到免費額度,而且不會因為閒置被暫停。額度還是有的 —— D1 免費方案每天 500 萬列讀、10 萬列寫,超過當日額度後,後續查詢會直接失敗,等 UTC 午夜重置。只是以這個專案的用量,差得很遠(Day 30 會算給你看)。

免費額度 閒置時暫停 這個專案的痛點
Supabase 500 MB DB 會 一週沒人用就要手動喚醒,寫連載時很致命
Firebase Spark 方案(每日 5 萬次文件讀取) 不會 Firestore 按文件讀寫次數計費,而活動列表、我的票券都是整批讀
Cloudflare Workers 10 萬請求/日 + D1 每日 500 萬列讀、10 萬列寫;單庫 500 MB 不會 ⭐ 採用

這一欄說的是會不會因為低活動被整個停掉,不是指冷啟動;免費額度三家都有。

但真正決定的不是這張表,是一個缺點:

D1 是 SQLite,沒有 SELECT ... FOR UPDATE。

這對一個要處理「開賣瞬間搶名額」的專案來說,聽起來像是選錯了。我知道,而我還是選了它 —— 因為那個限制正好是 Day 27 整篇的題目。一個沒有行鎖的資料庫,能不能靠其他機制守住不超賣,比直接用一個有行鎖的資料庫更值得做成實驗。

這個專案要做什麼

活動報名。限量名額、可以選位、保留十分鐘、逾時自動釋放、確認後出票。

不收錢。

對外它是報名系統,對內跑的是售票的規則 —— 限量、搶、狀態機、逾時。差別只在收不收錢,而規則的張力一點都不因為不收錢而減少。

15 條非目標

開賽前寫好的那兩份檔案今天正式上場。docs/non-goals.md 現在就在 repo 的 docs/ 底下,而它比功能清單重要。

挑五條最會被 AI 越過的:

# 不做 為什麼
3 線上金流 只記帳不收錢,範圍與合規都爆炸
4 真實場館座位圖 只做 10×10 方格,不畫舞台
11 管理後台 用 wrangler d1 execute 直接下 SQL
13 驗票掃碼入場 票券顯示 QR,不做掃描端
15 UI 精緻度 系統元件不炸掉即可

完整 15 條在 repo 的 docs/non-goals.md:github.com/n913239/event-signup-lab/blob/main/docs/non-goals.md


誰當裁判

部署這一步沒有測試可跑,所以裁判很原始:

curl -s https://<url>/health | grep '"ok":true'

會通就是通,不會通就是沒做完。第一天的驗收標準不需要更複雜。

這一篇留下的心法:

規格先行擋的是範圍擴張,擋不了空白 —— 空白要靠有人來問,而人不會問自己;它問了、我答了,那一條就不是擴張,是我的決定。規劃工具只替你守你餵給它的目標:我沒說「發文日不等工程」,它就把上線推到發文當天 —— 所以 phase 順序用它的,日期用我的。

明天:換一個乾淨的空目錄,讓另一個 AI 只拿一句需求設計 schema;跟 repo 裡那份讀過規格的版本,拿同一份 tests/schema.test.js 量。


參考資料

  • spec-kit(GitHub):github.com/github/spec-kit —— 本文用 v1.0.6(2026-09-10 發布),specify init --here --integration claude;上面兩處預設值出自 v1.0.6 的 templates/commands/specify.md(LIMIT: Maximum 3 [NEEDS CLARIFICATION] markers total)與 clarify.md(Maximum of 5 total questions across the whole session);相關產出(constitution、spec、plan、openapi.yaml、tasks)在 repo 的 .specify/ 與 specs/001-event-signup-full/(腳手架 be5d262、五步產出 bca4d9a);spec-writeback.md 是 clarify 那一輪依我的要求另外寫的回寫指引,不是 spec-kit 的標準產出
  • 本文實測:spec-kit 五步是 2026-09-14(Claude Code 2.1.270,同一個 session 五輪,$6.78;每步 git status 確認 docs/ 未動);部署是 2026-09-27
  • Cloudflare Workers 免費方案額度:developers.cloudflare.com/workers/platform/limits
  • Cloudflare D1 定價與限制:developers.cloudflare.com/d1/platform/pricing、d1/platform/limits(單庫 500 MB 在 limits 那頁)
  • 上面那張比較表的另兩家,數字取自官方定價頁(2026-10-05 查):Supabase Pricing(500 MB 資料庫、「Free projects are paused after 1 week of inactivity」)、Firebase Pricing(Spark 方案 Firestore 每日 50K document reads)
  • 活動報名 repo(docs/non-goals.md 完整 15 條):github.com/n913239/event-signup-lab

上一篇
Day 20 同一份需求,當年我直接寫了,今天讓 OpenSpec 先問
系列文
盡信 Claude,不如無 Code — 心法與全端實戰 共 21 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言