🎯 什麼情境該想到我
當一段程式把「取資料 + 轉換 + 驗證 + 輸出」全糊在一起,讀起來很費力時。
⚙️ 怎麼用
- 列出這段程式做了哪些「任務」(常常一寫就是好幾件)。
- 讓這些任務依序、分開進行:一個區塊/函式只推進一件任務。
- 必要時把每件任務提煉成獨立函式(見 工具-提煉函式)。
這條是「函式單一職責」在任何一段程式碼層級的應用,連函式內部都適用。
★ 實際操作:把任務列出來,再重排
《編寫可讀程式碼的藝術》的做法很機械——逐行標註這行在推進哪一件任務,然後把同一任務的行排在一起:
// ✗ 四件事交錯:解析、驗證、查價、組回應
func handleQuote(w http.ResponseWriter, r *http.Request) {
var req QuoteReq
if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "bad json", 400); return } // 解析
price, err := pricing.Lookup(r.Context(), req.SKU) // 查價
if req.Qty <= 0 { http.Error(w, "qty must be > 0", 400); return } // 驗證(放太晚:已經白查一次價)
if err != nil { http.Error(w, "lookup failed", 502); return } // 查價
resp := QuoteResp{SKU: req.SKU, Total: price * int64(req.Qty)} // 組回應
if req.Currency != "" && req.Currency != "TWD" { http.Error(w, "unsupported currency", 400); return } // 驗證(更晚)
json.NewEncoder(w).Encode(resp) // 組回應
}標完就看得出問題:驗證散在三個地方,其中兩個在做完昂貴的查價之後。重排成四段:
func handleQuote(w http.ResponseWriter, r *http.Request) {
req, err := decodeQuote(r) // 1. 解析
if err != nil { writeErr(w, 400, err); return }
if err := req.Validate(); err != nil { // 2. 驗證(全部集中、最先做、不碰外部資源)
writeErr(w, 400, err); return
}
price, err := pricing.Lookup(r.Context(), req.SKU) // 3. 做事
if err != nil { writeErr(w, 502, err); return }
writeJSON(w, QuoteResp{SKU: req.SKU, Total: price * int64(req.Qty)}) // 4. 輸出
}重排之後順便修掉了一個效能 bug(無效請求不再打外部服務)——這很常見:任務交錯的程式碼,通常也藏著順序錯誤。
★ 常見的「兩件事黏在一起」
| 黏在一起的 | 拆開後 |
|---|---|
計算 + 輸出(邊算邊 fmt.Println) | 先算出結果值,再一次輸出——計算部分才能被測試 |
| 讀取 + 轉換 + 寫入(邊讀邊改邊寫) | 讀成資料結構 → 純函式轉換 → 寫出;中間那段零 I/O、最好測 |
| 走訪 + 篩選 + 累加(一個巨大 for) | 真的影響可讀性才拆;Go 沒有內建 Map/Filter,硬拆成三個迴圈有時反而更長 |
| 業務判斷 + 錯誤處理 | 錯誤用 guard clause 提早 return,主流程靠左 |
判準:每一段任務能不能用一句話命名?能 → 它可以是一個區塊或函式;某段要用「和」連接兩件事 → 再拆。
🧪 我實際套用的紀錄
- 2026-07-14:(待填)
⚠️ 注意
-
「一件事」是相對的抽象層次判斷;能被有意義地再分,就還沒到位。
-
別為了「一件事」把效能拆掉:三次走訪同一個百萬筆的 slice,跟一次走訪做三件事,差的是記憶體頻寬。熱點上的迴圈合併是合理的——但要留一行註解說為什麼(這正是 why 註解,見 工具-註解是必要之惡)。
🔗 相關工具
- 工具-函式短小且單一職責 —— 同一件事的函式層說法,這張講「一段程式碼」,它講「一個函式」的邊界
- 工具-縮小變數作用域 —— 配套手法:把任務拆開後,順手把各段的變數關進各自的範圍,心智負擔才真的降下來
- 工具-提煉函式 —— 拆解的執行工具:辨認出「這段在做另一件事」之後,用它把那段搬出去命名