🎯 什麼情境該想到我
當你遇到「一長串複雜的 if 判斷條件」或「then/else 分支裡塞了一大坨程式碼」,得逐字讀完才知道它在判斷什麼、做什麼的時候。
⚙️ 怎麼用(步驟 / 公式)
意圖:複雜的條件邏輯是製造難懂程式碼的元兇之一。把「判斷條件」和「各分支的動作」分別提煉成有意義名字的函式,讓程式碼變成一段自我說明的敘述——你一眼看到函式名就懂它在做什麼,不必再讀細節。
小步安全進行:
- 對「條件判斷式」施以提煉函式,取一個能說明「這在判斷什麼」的名字(如
isSummer())。 - 對「then 分支」施以提煉函式,取一個說明「這在做什麼」的名字(如
summerCharge())。 - 對「else 分支」同樣提煉成具名函式。
- 每提煉一段就執行一次測試。
★ before / after(Go)
// before:條件和兩個分支都是「怎麼算」,讀者得自己翻譯成「在幹嘛」
if !date.Before(plan.SummerStart) && !date.After(plan.SummerEnd) {
charge = float64(quantity) * plan.SummerRate
} else {
charge = float64(quantity)*plan.RegularRate + plan.RegularServiceCharge
}
// after:三個名字取代三段運算
if summer(date, plan) {
charge = summerCharge(quantity, plan)
} else {
charge = regularCharge(quantity, plan)
}★ 這個手法就是 提煉函式 專門用在條件式上的版本。
價值在於條件式是最容易寫錯又最難讀的地方:!a.Before(b) && !a.After(c)
究竟含不含端點?給它一個名字inRange,然後為那個名字寫一個測試——邊界只需要被想清楚一次。
★ Go 的三種落地方式
// ① 具名布林變數(最輕)
isSummer := !date.Before(plan.SummerStart) && !date.After(plan.SummerEnd)
// ⚠️ 前提:運算式是純的。含 nil 判斷就不能提(見 [[提煉變數]] 的短路陷阱)
// ② 函式
func inSummer(d time.Time, p Plan) bool { … }
// ③ ★ 方法 —— 最好,因為邏輯跟資料在一起,而且可以獨立測試
func (p Plan) IsSummer(d time.Time) bool {
return !d.Before(p.SummerStart) && !d.After(p.SummerEnd)
}★ 優先選 ③。條件式幾乎總是在問「某個資料處於什麼狀態」,那是那個型別的責任。
★★ 條件式命名的三個實務規則
① 名字要說「是什麼狀態」,不要說「怎麼判斷」
if isEligibleForDiscount(c) { … } // ✅
if ageGreaterThan65AndMember(c) { … } // ★ 名字只是把程式碼唸一遍② 不要對否定命名
if !isNotReady(x) { … } // ★ 雙重否定,讀者要在腦裡做兩次反轉
if isReady(x) { … } // ✅③ Go 慣例:回傳 bool 的方法用 Is/Has/Can 開頭,且不要叫 GetXxx
IsExpired()、HasPermission()、CanRetry()。見 工具-Go命名慣例。
⚠️ Go 的陷阱:error 分支不要硬套
// ★ 不要「分解」錯誤處理
if err := f(); err != nil {
return fmt.Errorf("f: %w", err)
}Go 的 if err != nil 是慣用語(idiom),讀者的眼睛會自動跳過它。
把它包成 if failed(err) { … } 讓它變得不像慣用語,反而更難讀。
→ 分解條件式的對象是「業務邏輯的條件」,不是控制流程的樣板。
和相鄰手法的分工
| 差別 | |
|---|---|
| 合併條件式 | 先合併(多個條件同一結果 → 一個條件),再分解(給那個條件一個名字) |
| 以衛述句取代嵌套條件式 | 處理嵌套深度;分解條件式處理單一條件的可讀性 |
| 以多型取代條件式 | 條件是在依型別分派時才升級到多型 |
🧪 我實際套用的紀錄
- (待填)
⚠️ 注意 / 什麼時候不適用
-
條件與分支若本來就短小易懂,硬提煉反而多繞一層,得不償失。
-
這是「讓意圖浮現」的手法,重點是取好名字;若名字取不出意義,代表你還沒真的理解它。
-
★ 被提煉的條件必須是純的。含
x != nil &&的短路保護不能拆出來(見 提煉變數)。 -
★ 不要對
if err != nil動手。Go 的錯誤處理樣板是慣用語,包裝它會讓程式更難讀。 -
簡單條件不要提煉:
if n > 0、if s == ""已經是最清楚的說法。 -
只有兩個分支、各一行時,分解常是過度工程。判準還是「意圖與實作的距離」。
-
分解完要有測試。提煉出
inSummer()卻不為它寫邊界測試,等於只是把 bug 搬到一個有名字的地方。
🔗 相關工具
- 提煉函式 Extract Function(本手法就是把提煉函式套用在條件與各分支上)
- 以衛述句取代嵌套條件式 Replace Nested Conditional with Guard Clauses(另一種整理條件結構的手法,處理巢狀)
- 合併條件式 Consolidate Conditional Expression(處理多個導向相同結果的條件)
- 重構
- 合併條件式 —— 通常先合併再分解
- 以衛述句取代嵌套條件式 —— 處理嵌套深度
- 提煉函式 —— 本手法的一般形式
- 提煉變數 —— 用具名變數的輕量版,以及它的短路陷阱
- 工具-Go命名慣例 —— 布林方法的命名