🎯 什麼情境該想到我

當你遇到「一長串複雜的 if 判斷條件」或「then/else 分支裡塞了一大坨程式碼」,得逐字讀完才知道它在判斷什麼、做什麼的時候。

⚙️ 怎麼用(步驟 / 公式)

意圖:複雜的條件邏輯是製造難懂程式碼的元兇之一。把「判斷條件」和「各分支的動作」分別提煉成有意義名字的函式,讓程式碼變成一段自我說明的敘述——你一眼看到函式名就懂它在做什麼,不必再讀細節。

小步安全進行:

  1. 對「條件判斷式」施以提煉函式,取一個能說明「這在判斷什麼」的名字(如 isSummer())。
  2. 對「then 分支」施以提煉函式,取一個說明「這在做什麼」的名字(如 summerCharge())。
  3. 對「else 分支」同樣提煉成具名函式。
  4. 每提煉一段就執行一次測試。

★ 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 搬到一個有名字的地方。

🔗 相關工具