🎯 什麼情境該想到我

分工:這張原本出自 Java 語境的《程式碼整潔之道》;Go 沒有例外,所以這裡講的是「原則怎麼翻譯到 Go 與 TS」。Go 錯誤處理的完整機制(%w、errors.Is/AsType/Join)→ 工具-Go現代錯誤處理;該包還是該轉、錯誤字串格式 → 工具-Go錯誤該包還是該轉。

當你的程式碼被一堆「檢查回傳值/錯誤碼」的 if 淹沒,或常被 null 炸到時。

⚙️ 怎麼用

  1. 用例外(Exception)而非回傳錯誤碼:讓正常流程乾淨,錯誤處理分離。
  2. 別回傳 null,也別傳 null 當參數:改回傳空集合、Optional,或丟例外。
  3. 例外訊息要有情境:說清楚哪裡、為什麼失敗。
  4. 把 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.4 net/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 任何值。

🔗 相關工具