🎯 什麼情境該想到我
分工:這張原本出自 Java 語境的《程式碼整潔之道》;Go 沒有例外,所以這裡講的是「原則怎麼翻譯到 Go 與 TS」。Go 錯誤處理的完整機制(
%w、errors.Is/AsType/Join)→ 工具-Go現代錯誤處理;該包還是該轉、錯誤字串格式 → 工具-Go錯誤該包還是該轉。
當你的程式碼被一堆「檢查回傳值/錯誤碼」的 if 淹沒,或常被 null 炸到時。
⚙️ 怎麼用
- 用例外(Exception)而非回傳錯誤碼:讓正常流程乾淨,錯誤處理分離。
- 別回傳 null,也別傳 null 當參數:改回傳空集合、Optional,或丟例外。
- 例外訊息要有情境:說清楚哪裡、為什麼失敗。
- 把 try/catch 的主體抽成獨立函式,讓錯誤處理成為「一件事」。
★ 原則 vs 語法:哪些能帶走、哪些不能
| 書中原則 | 背後的目的 | Go 的做法 | TS 的做法 |
|---|---|---|---|
| 用例外取代錯誤碼 | 正常流程不被錯誤檢查淹沒 | 不適用字面意思。Go 用 error 回傳值;用 guard clause(提早 return) 讓主流程靠左 | throw + 在邊界 try/catch;或回傳 Result<T, E> |
| 例外要帶情境 | 看錯誤訊息就知道哪裡、為何 | fmt.Errorf("load user %d: %w", id, err) 逐層加情境 | new Error("load user", { cause: err })(ES2022 cause) |
| 把 try 主體抽成函式 | 錯誤處理是「一件事」 | 把「一串可能失敗的步驟」收進一個回傳 error 的函式,呼叫端只處理一次 | 同書中做法 |
| 別回傳 null | 呼叫端忘了檢查就炸 | 見下一節 | 開 strictNullChecks,讓編譯器逼你處理 undefined |
| 別用例外控制流程 | 例外是給意外 | panic 只給程式錯誤(不該發生的狀態);可預期的失敗一律回 error | 別用 throw 做迴圈跳出或分支 |
Guard clause 就是 Go 版的「讓正常流程乾淨」:
func Ship(ctx context.Context, id OrderID) error {
o, err := repo.Get(ctx, id)
if err != nil {
return fmt.Errorf("get order %s: %w", id, err)
}
if o.Status != Paid {
return ErrNotPaid
}
// 走到這裡的都是「正常情況」,不用再縮排
return carrier.Book(ctx, o.Address)
}★ 「別回傳 null」在 Go 的四種對應
| 情況 | 做法 | 為什麼 |
|---|---|---|
| 回傳集合,查無資料 | 回傳 nil slice 通常就好:len、range、append 對 nil 都安全 | 但要注意 JSON:json.Marshal 把 nil slice 編成 null、空 slice 編成 [](Go 1.26.4 實測,2026-09-25)——API 回應要 [] 就得明確 make([]T, 0) |
| 可能沒有值 | comma-ok:func Find(id) (User, bool) | 呼叫端被語法逼著看到 ok |
| 找不到是一種錯誤 | 回傳哨兵錯誤 ErrNotFound,呼叫端 errors.Is | 讓「找不到」與「DB 掛了」可以分開處理 |
| 指標可能為 nil | 能用值型別就用值型別;必須用指標時在文件註解寫明「可能為 nil」 | nil map 可讀不可寫:讀回零值、寫入會 panic |
★ panic 與 recover 的邊界
- panic 用在:「如果發生,代表程式本身有 bug」——陣列越界、不可能的 switch 分支、
init時設定檔缺必要欄位(regexp.MustCompile這類Must函式就是這個語義)。 - recover 只放在邊界:每個請求、每個 worker goroutine 的最外層,把 panic 轉成錯誤回應 + log,避免整個程序掛掉。
net/http已經幫你 recover 了,但只到一半:它在「處理這條連線的 goroutine」裡 recover handler 的 panic 並記 log(http: panic serving …,見 Go 1.26.4net/http/server.go);handler 裡自己go func()開的 goroutine 若 panic,沒有人接,整個程序直接結束。所以自己開的 goroutine 要自己包 recover,或改用會回收錯誤的errgroup。
🧪 我實際套用的紀錄
- 2026-07-14:(待填)
⚠️ 注意
-
別用例外控制正常流程;例外是給「意外」用的。
-
Go 裡「處理 or 回傳,只做一件」:同一個錯誤又 log 又 return,上層再 log 一次,最後一個錯誤在 log 裡出現三次。詳細規則見 工具-Go錯誤該包還是該轉。
-
TS 的
catch (e)型別是unknown(strict下):先用e instanceof Error收窄再讀.message,別假設它一定是Error——JS 允許throw任何值。
🔗 相關工具
- 工具-函式短小且單一職責 —— 直接效果,錯誤處理抽走之後主流程才看得出在做什麼
- 工具-防禦式編程 —— 界線劃分,哪些該丟例外、哪些該在邊界就擋掉,用它判斷