🎯 什麼情境該想到我

當你「有一個複雜的運算式,得盯著它拆解才知道各部分是什麼意思」的時候——把其中有意義的一段抽成有名字的區域變數,用名字說明它代表什麼,讓整個式子好讀、也好除錯。

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

意圖:把複雜運算式的一部分(或全部)抽成一個有名字的解釋性區域變數。

動作要點(小步安全進行):

  1. 確認要抽出的那段運算式沒有副作用。
  2. 宣告一個不可變的(如 const/final)新變數,把那段運算式指派給它,取一個能說明「它是什麼」的名字。
  3. 把原運算式中的那段替換成這個新變數。
  4. 編譯並測試。
  5. 若同一段運算式出現多次,逐一替換。

★ before / after(Go)

before:

return order.Quantity*order.ItemPrice -
    math.Max(0, float64(order.Quantity-500))*order.ItemPrice*0.05 +
    math.Min(order.Quantity*order.ItemPrice*0.1, 100)

after——每個名字都在解釋一段運算式的意義:

basePrice := order.Quantity * order.ItemPrice
quantityDiscount := math.Max(0, float64(order.Quantity-500)) * order.ItemPrice * 0.05
shipping := math.Min(basePrice*0.1, 100)
return basePrice - quantityDiscount + shipping

★ 判準:這個名字在「函式之外」也講得通嗎?
講得通(basePrice、shipping 是領域詞彙)→ 其實該做 以查詢取代暫時變數 或搬到型別上變成方法。
只在這個函式裡有意義 → 停在區域變數就好。

★★ 最容易踩的失效條件:短路求值被打斷

// before:&& 的右邊只有在 u != nil 時才會求值
if u != nil && u.Profile.Active { ... }
 
// after:★ 壞了 —— u 是 nil 時直接 panic
active := u.Profile.Active
if u != nil && active { ... }

提煉變數會把「延後求值」變成「立刻求值」。 只要被提煉的運算式
可能 panic、可能很慢、或有副作用,這個重構就不是行為保持的。

同一個陷阱的另外兩張臉:

v, ok := m[k]                    // map 取值本身安全
n := len(*p)                     // ★ 但 *p 在 p == nil 時 panic
x := arr[i]                       // ★ i 還沒經過範圍檢查

→ 只提煉「純」的運算式。不純的,改成提煉整個 if 分支(分解條件式)。

Go 的兩個小細節

  • if 內宣告的變數作用域只到那個 if:if n := f(); n > 0 { … } else { /* n 在這裡也看得到 */ }。
    提煉時把變數移出 if → 作用域變大,可能和後面的名字撞到(Go 會編譯錯誤,或更糟——靜默 shadow)。
    ⚠️ shadow 檢查不在 go vet 的預設集合裡;要靠 golangci-lint 把 govet 的 shadow 打開,或單獨裝 golang.org/x/tools/go/analysis/passes/shadow。
  • 提煉變數不會讓值逃逸到 heap,除非你取了它的位址或被 closure 捕獲。用 go build -gcflags=-m 看有沒有 moved to heap。
    → 熱路徑上提煉變數通常零成本,不必為效能猶豫。

TypeScript

// ★ 提煉 optional chaining 時要小心:?. 也是短路的
const active = user?.profile?.active;   // 安全
const n = user!.profile.active;         // ★ 非空斷言把檢查關掉了,等於埋雷

🧪 我實際套用的紀錄

  • (待填)

⚠️ 注意 / 什麼時候不適用

  • 若這個名字只在單一函式內有意義,用區域變數就好;若在整個物件範圍都值得解釋,考慮改用改變函式宣告 Change Function Declaration提出一個查詢函式。

  • 若運算式本身已經夠清楚,多一個變數反而是雜訊,就別抽,甚至該反向做行內變數 Inline Variable。

  • 反向手法:行內變數 Inline Variable。

  • ★ 被提煉的運算式必須是「純」的(不 panic、無副作用、不昂貴),否則短路求值被打斷(見上)。

  • 名字只是重述運算式就不要提煉:isTrue := x == true、sum := a + b 沒有任何資訊增益。

  • 提煉出五、六個變數 → 訊號是「這個函式該拆了」,不是「命名成功了」。接著做 提煉函式。

  • 迴圈內提煉變數要注意 closure 捕獲。Go 1.22 起迴圈變數改為每輪新建,行為與 1.21 以前不同——go.mod 的 go 版本低於 1.22 時仍是舊語義。

🔗 相關工具