🎯 什麼情境該想到我

當你「一個區域變數的名字並沒有比它所指的運算式帶來更多意義」的時候——直接用運算式取代這個變數,去掉多餘的一層,讓程式更精簡,也常常是為了讓後續重構更好做。

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

意圖:當變數名沒比運算式本身帶來更多意義時,用運算式取代它。

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

  1. 確認指派給該變數的運算式沒有副作用。
  2. 若該變數尚未宣告為不可變,先設為不可變並測試,確認它只被賦值一次。
  3. 找出第一處使用該變數的地方,換成它的運算式。
  4. 編譯、測試。
  5. 逐一替換其餘使用處,每次都測試。
  6. 全部替換後刪除該變數的宣告與賦值。

★ before / after(Go)

// before:名字沒比運算式本身清楚
basePrice := anOrder.BasePrice()
return basePrice > 1000
 
// after
return anOrder.BasePrice() > 1000

什麼時候該 inline 掉一個變數

訊號例子
名字只是重述運算式basePrice := o.BasePrice()
只用一次,且緊接在後err := f(); if err != nil { → if err := f(); err != nil {
妨礙下一步重構要 搬移函式 前,先把只為了它存在的暫時變數消掉

★ Go 的慣用寫法本身就是這個手法:

// ✅ Go 慣例:把 err 收進 if 的作用域,離它被用到的地方最近
if err := db.Ping(ctx); err != nil {
    return fmt.Errorf("ping: %w", err)
}

★★ 失效條件:求值次數與時機都變了

這是 提煉變數 陷阱的鏡像。變數求值一次;inline 之後,每個出現的地方都求值一次。

// before:now 整段流程都是同一個時間點
now := time.Now()
rec.CreatedAt, rec.UpdatedAt = now, now
 
// after:★ 壞了 —— 兩個欄位會差幾奈秒,測試裡的 == 比較開始隨機失敗
rec.CreatedAt, rec.UpdatedAt = time.Now(), time.Now()

只要右側是這其中之一,就不要 inline:

  • 不確定的來源:time.Now()、rand.Int()、uuid.New()
  • 有副作用:ch <- v、counter.Add(1)、讀取 io.Reader(★ 讀兩次 = 拿到不同資料)
  • 昂貴:DB 查詢、HTTP 呼叫、正則編譯

Go 的具名回傳值

func f() (result int) {
    result = compute()
    return result        // ★ 這個 return result 不能 inline 成 return compute()
}                        //   如果有 defer 在改 result 的話

有 defer 在動具名回傳值時,return x 和 return f() 的語義不同——先確認沒有 defer 碰它。

工具支援

gopls 有 refactor.inline.variable,且被官方列為較嚴謹的操作。仍會掉註解。

🧪 我實際套用的紀錄

  • (待填)

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

  • 若變數名有解釋意圖的作用、或運算式重複計算成本高、或它讓除錯更容易,就保留變數,甚至反向做提煉變數 Extract Variable。

  • 反向手法:提煉變數 Extract Variable。

  • ★ 右側不是純運算式就不要 inline:time.Now()、rand、讀 io.Reader、DB/HTTP 呼叫——求值次數變多就壞了(見上)。

  • inline 後那行超過 100 字元就別做。目的是更好讀,不是更少行。

  • 有 defer 在修改具名回傳值時,return x 不等於 return f()。

  • 不要順手做。行內變數常是為了「解開下一步重構的路」;如果沒有下一步,多半是在製造 diff 噪音。

🔗 相關工具