🎯 什麼情境該想到我
分工:這張是快速決策卡——什麼時候提煉、名字怎麼取、何時收手。完整的機械步驟、Go 裡提煉時
defer/recover/內聯預算的陷阱、gopls 的支援程度 → 提煉函式(型錄頁);反向操作 → 行內函式。
當你有一段程式碼「可以獨立命名、說明它在做什麼」,或想拆長函式、消重複時。最常用的重構手法。
⚙️ 怎麼用
- 找出一段內聚的程式碼(常是「這段在算 X」)。
- 抽成新函式,用意圖命名(名字說「做什麼」而非「怎麼做」)。
- 把用到的區域變數當參數傳入,回傳需要的結果。
- 原處改成呼叫新函式,跑測試確認行為不變。
好處:消除重複、讓函式各層抽象一致、用函式名取代解釋性註解。
★ 什麼時候該提煉:四個訊號
| 訊號 | 例子 |
|---|---|
| 你想在一段程式碼上面寫註解說它在做什麼 | // 計算運費 下面 8 行 → shippingFee(order) |
| 同一段邏輯出現第三次 | 兩次還可以忍,三次就是一個概念,該有名字(見 工具-DRY原則) |
| 一個區塊的抽象層次跟周圍不同 | 業務流程中間突然出現位元運算或字串切割 |
| 你看不懂一段程式碼,花了五分鐘終於懂了 | 立刻把你的理解變成函式名,下一個人就不用再花五分鐘 |
★ 名字取「做什麼」,不取「怎麼做」
| 怎麼做(✗) | 做什麼(✓) |
|---|---|
loopAndSumPrices(items) | subtotal(items) |
checkMapAndDB(id) | findUser(id) |
parseSplitTrim(s) | parseTags(s) |
判準:實作換掉之後,名字還正確嗎? findUser 不管查快取還是查 DB 都對;checkMapAndDB 一改實作就說謊。
★ 提煉後要檢查的三件事
- 參數個數:提煉出來的函式需要 5 個參數 → 切點選錯了,或這些資料其實該是一個 struct(引入參數物件)。
- 有沒有改到副作用順序:原本夾在兩個 I/O 中間的程式碼,搬出去後是否還在同一個時間點執行?尤其是
defer——在新函式裡的defer會在新函式結束時執行,而不是原函式結束時。 - 呼叫處讀起來像不像一句話:
if isEligible(user, plan)✓;if check(u, p, true, nil)✗。
★ 何時收手(不要提煉)
- 提煉出來的函式只有一行,而那一行本身已經很清楚(
return a + b)。 - 讀者每次都得跳進去才能理解呼叫處——名字沒有提供新資訊,只是多一次跳轉。
- 為了提煉,得把 4 個區域變數改成指標參數傳進去修改:這代表這段邏輯和周圍狀態糾纏太深,先處理狀態(拆資料結構)再提煉。
🧪 我實際套用的紀錄
- 2026-07-14:(待填)
⚠️ 注意
- 若抽出後參數爆多,代表切點不對或該連資料一起搬(考慮搬移到別的類別)。
🔗 相關工具
- 工具-函式短小且單一職責 —— 提煉的收手判準:抽到「一個函式只做一件事」為止,回答「要拆到多細」
- 工具-小步重構 —— 提煉要在測試保護下一小步一小步做,這張講怎麼保證每一步都沒改到行為
- 工具-註解是必要之惡 —— 「有註解在解釋這段 code」正是該提煉的訊號:用函式名取代那句註解