iT邦幫忙

2026 iThome 鐵人賽

DAY 15
0
Vibe Coding

從 GAS 到 Cloudflare D1:AI Agent 如何打造、接手並重構真實便當系統系列 第 15 篇

Day 15|Debug 最有價值的不是猜,而是把證據串起來

  • 分享至 

  • xImage
  •  

Bento Day 15

功能開始跨前端、Worker 後端、Cloudflare D1 資料庫、身份狀態與快取之後,Debug 很容易變成一場猜謎。

使用者只看到一個錯誤畫面,AI 卻可以立刻提出很多可能:

  • session 壞了;
  • API response 不對;
  • D1 資料不一致;
  • role mapping 有問題;
  • frontend cache 沒更新;
  • route guard 判錯狀態。

每一個都可能。

問題是,如果一開始就同時追六個方向,模型越會推理,反而越容易把 Context 塞滿,最後得到一個「聽起來很合理」但沒有被證據支持的故事。

Debug 的第一步不是找最像答案的原因,而是用證據把可能出錯的系統範圍一層一層縮小。

工程上常把這個「仍可能出錯的範圍」稱為 fault domain。

同一個症狀,可以來自完全不同的層

Day 11 提過 fresh 員編登入的案例。

畫面最後被導向不預期的 LINE 流程,看起來很像 Auth 壞掉;但沿著資料流往下看,使用者看到的症狀,和最早出錯的那一層,不一定相同。

Day 15 的主案例更直接。

2026-09-21 的便當系統曾在建立訂單時回傳 MUTATION_CONFLICT。如果只從錯誤名稱出發,很容易先懷疑:

  • 版本/狀態衝突(optimistic concurrency)判斷;
  • 訂單寫入保護(mutation protection);
  • 訂購日曆狀態;
  • 使用者身份;
  • D1 當下資料。

但 repo 的 changelog 已經留下確認原因:

系統把不同版本菜單整理成當日可用結果時,沒有保留資料庫既有的 menu_item_id,後續反而拿到臨時產生的 ID。建立訂單時,這個 ID 找不到對應的持久化資料,資料庫外鍵檢查失敗,最後才以 MUTATION_CONFLICT 浮到上層。

這個事件真正值得留下來的,是下面這個差異:

使用者看到 MUTATION_CONFLICT
≠
最早出錯的位置就在 mutation conflict handling

畫面上的錯誤,只是最後被觀察到的症狀。

Evidence-first Debug 的順序

遇到這類跨層問題,我會先問三題:

1. 哪一層最早出現「預期值 ≠ 實際值」?
2. 前一層輸出的資料是否仍然正確?
3. 後面的錯誤是不是只是在投影前面已經發生的問題?

我要找的是 first diverging point。

第一次看到這個詞,可以把它直接理解成:

預期資料和實際資料第一次開始不一樣的位置。

以 9/21 這次事件來看,Debug 若只盯著最外層的 MUTATION_CONFLICT,範圍仍然很大。

把資料流攤開後,問題可以改寫成:

資料庫既有餐點
→ 菜單整理層
→ 建立訂單時使用的餐點 ID
→ Worker 建立訂單明細
→ D1 外鍵檢查
→ MUTATION_CONFLICT

Bento Day 15 first diverging point

已確認的 changelog 指出:資料庫裡原本的餐點 ID 並沒有被正確帶過整理層,後續流程拿到的已不是同一個 menu_item_id。

這時 first diverging point 就往前收斂到:

資料庫原始餐點 ID
        ↓
菜單整理層
        ↓
餐點 ID 在這裡開始分岔

一旦找到這個點,後面的 foreign-key failure 和 MUTATION_CONFLICT 就比較像「後果」,而不是第一個該修改的地方。

Evidence 的作用,是淘汰假設

Evidence-first Debug 的重點不在收集更多 log,而在讓每一份 Evidence 都能刪掉一部分可能性。

如果只根據畫面上的錯誤,調查初期可以先列出幾個待驗證的候選假設:

Hypothesis A
order mutation 的 concurrency guard 誤判。

Hypothesis B
calendar / menu 狀態讓這筆訂單變成非法操作。

Hypothesis C
menu identity 在進入 mutation 前已經和 persisted data 分岔。

接著不要問「哪個最像」,而是問每個假設需要什麼證據。

A 需要:
- mutation guard 的輸入
- version / state 是否真的衝突

B 需要:
- 當日 calendar / menu effective state
- 該餐點是否仍可被訂購

C 需要:
- persisted menu_item_id
- normalized overlay 輸出的 menu item identity
- 寫入 order_items 前實際使用的 ID

當已確認的 Evidence 指向「整理層沒有保留原本的 menu_item_id」,其他候選方向就不需要再和這條線用相同優先級繼續追。

Debug 的進度可以直接看「還剩多少可能」:

原本有三個可能,現在只剩一個仍和 Evidence 一致。

唯讀 Evidence 優先

這種方法在 Production Data 上更重要。

看到 foreign-key failure,最危險的反應之一,是直接把它理解成「資料壞了」,然後準備修 D1。

但這次 incident 反而提醒我:

資料庫拒絕一個不存在的 synthetic ID,本身可能是在正確保護資料。

如果資料庫裡原本的餐點資料沒有問題,需要修的是中間層如何傳遞餐點 ID,而不是先改 Production Data 去配合錯誤的結果。

所以碰到資料問題時,我會先要求唯讀檢查:

  • 資料來源裡原本的 record 是什麼;
  • 中間整理層產出了什麼;
  • API 或寫入動作實際收到什麼;
  • 哪一層第一次和原始資料不一致。

能先用 read-only Evidence 排除的,就不要先用資料修改來猜答案。

把假設寫成可以被否證的句子

「可能是 session 有問題」太寬,因為它沒有告訴下一步要查什麼。

比較可操作的寫法是:

Hypothesis:
normalized overlay 沒有保留 persisted menu identity,
導致 order mutation 使用不存在的 menu_item_id。

Evidence needed:
- persisted menu_item_id
- overlay output identity
- mutation input identity

Evidence 一旦與假設衝突,就刪掉這條線。對 AI 來說,工具結果因此不只是「多一份資訊」,而是能直接剪掉搜尋空間。

沿資料流追查,比一次讀完整個 Repo 更省 Context

Agent Debug 常為了避免漏看,先把所有疑似相關檔案讀進 Context;這有時必要,但不該是第一步。

比較有效的順序是:

畫面上的症狀
→ 最近可觀察的狀態
→ 產生這個狀態的 function / API
→ 上游整理層
→ 原始資料來源

每走一層只問一件事:

這一層的輸出,和預期還一致嗎?

找到 first diverging point 後,已證明正常的區域就能退出調查。這同時省 Context,也降低「剛好讀到某段程式碼,就把它誤認成 root cause」的機會。

找到 root cause 後,只改 Evidence 足以支持的地方

9/21 這次 incident 最後留下的修正很有代表性:

菜單整理層應保留資料庫既有的 menu_item_id。

這個修改範圍和 Evidence 對得上。

不需要因為錯誤最後出現在 mutation path,就順手重寫 mutation protection;也不需要因為碰到 foreign key,就去改 schema 或 Production data。

可以把這個原則寫成:

symptom
↓
trace
↓
first diverging point
↓
root cause supported by evidence
↓
smallest justified mutation

這裡的 smallest justified mutation,可以直接理解成:

只修改目前 Evidence 足以證明需要修改的最小範圍。

這也和前幾天的 Scope / Contract / Regression 串得起來:Debug 負責找出「哪裡真的錯」,而不是替一次 incident 打開新的重構範圍。

Debug 完成的標準,不是「找到一個說得通的故事」

AI 很擅長補出完整 causal story;工程上更重要的是,這個故事能不能被觀察結果逐段支持:

root cause 能解釋症狀?
↓
和實際 Evidence 一致?
↓
修改後,原本分岔的 Evidence 恢復?

如果三題還答不出來,狀態就應該留在「仍在調查」。

Day 15 最後留下四條規則:

  1. 從症狀往 source of truth 追,先找 first diverging point。
  2. 假設要能被 Evidence 否證,Evidence 的作用是淘汰可能性。
  3. Production Data 優先用 read-only Evidence 確認,不先改資料配合猜測。
  4. 只修改 Evidence 足以支持的最小範圍,修改後回頭驗證原本的分岔點。

好的 Debug,會讓還可能成立的答案越來越少,而不是讓猜測越來越漂亮。

下一篇會再往 Production 前進一步:root cause 找到、bounded change 完成後,什麼樣的 Evidence 才足以讓 Worker-only 變更直接部署?

本系列實作專案

這是一套持續開發中的便當訂購系統。文章著重「為什麼」,GitHub 保留實際程式碼、文件與演進紀錄。

GitHub:https://github.com/henryfir456/bento-order-app


上一篇
Day 14|測試不是最後補上去的,而是系統的「業務記憶」
下一篇
Day 16|哪一類 Worker-only 變更,我才敢讓它直接進 Production?
系列文
從 GAS 到 Cloudflare D1:AI Agent 如何打造、接手並重構真實便當系統 共 17 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言