《Refactoring 重構》Podcast 準備稿:搬移特性
書名: 重構 Refactoring(Improving the Design of Existing Code, 2nd Edition) 作者: Martin Fowler 系列: 啃一本大書(恩普拉氏) 公開標題建議: 啃一本大書|Refactoring.搬移特性:讓相關的東西住在一起 (5/8) 涵蓋範圍: 第8章 Moving Features(搬移特性)
背景速覽
封裝(EP4)解決「東西該藏多少」,這集處理「東西該放哪」。第8章 Moving Features 談的是把函式、欄位、敘述句搬到它們更該待的位置——Move Function / Field、Move Statements、Slide Statements、Split Loop、Replace Loop with Pipeline、Remove Dead Code。這類重構的共同精神:模組化的關鍵,是讓相關的元素聚在一起、無關的元素分開。而「什麼相關」這件事,是隨著你越來越懂這段程式才慢慢看清楚的。
一句話重點
決定一段邏輯該放哪裡,看的是它跟誰互動得最多——一個函式如果一直在動別人家的資料,它就該搬去那個資料所在的地方;搬移特性就是不斷把元素重新歸位,讓每次歸位都反映你對問題理解的加深。
值得討論的重點
1. Move Function / Move Field:跟著「互動」走
搬移的判準很清楚:一個函式(或欄位)參考「別的環境」的元素,比參考「當前環境」的還多時,就該搬過去。 這正是 EP2 講的 Feature Envy(依戀情結)味道的解法——函式對別人家的資料更有興趣,那就讓它搬去那個家。搬移通常還能順帶改善封裝(搬過去後,原本要對外公開的資料可以變回 private)。
Fowler 有句很實在的話:「這個決定不容易,而越困難的選擇往往越不重要。」 意思是,如果你糾結半天不知道一個函式該放 A 還是 B,那通常表示放哪都差不多——別卡在那裡,選一個、之後不對再搬回來(重構是可逆的)。
2. Move / Slide Statements:把相關的敘述句靠在一起
比搬函式更細的,是搬「敘述句」:
- Slide Statements(滑動敘述句):把相關的程式碼行移到一起(例如把一個變數的宣告,挪到第一次使用它的地方旁邊)。這讓「哪些行是一夥的」變得肉眼可見,也是 Extract Function 的前置動作——先把要抽的東西滑到一起,再抽就乾淨了。
- Move Statements into/to Function:把重複出現在多個呼叫端的敘述句,往函式內部收(或反向往外推)。
這些是很微觀但很高頻的「整地」動作,常常是大重構之前的鋪路。
3. Split Loop:一個迴圈只做一件事
一個迴圈裡同時算總和、又算平均、又順便印東西,是很常見的「省一個迴圈」式優化,但它讓這幾件事綁死在一起、難以單獨修改。Split Loop 把它拆成幾個各做一件事的迴圈。聽眾一定會問效能——多跑幾次迴圈不是變慢?Fowler 的回答和 EP1/EP3 一致:先求清楚,效能等量測出瓶頸再說;而且拆開後每個迴圈通常更容易進一步優化(甚至換成 Pipeline)。
4. Replace Loop with Pipeline:用集合管線取代手寫迴圈
對應 EP2 的 Loops 味道。傳統 for 迴圈裡,「在過濾什麼、在轉換什麼」全混在迴圈體裡,要讀進去才懂。換成集合管線(filter → map → reduce),資料像水一樣由上而下流過一串具名操作,你一眼就能讀出「過濾偶數 → 平方 → 收集」這條處理鏈。這是現代語言(JS、Python、Java Stream)帶來的可讀性升級。
5. Remove Dead Code:刪掉就是了
死 code(永遠走不到的分支、沒人呼叫的函式)會增加閱讀者的認知負擔——他得花力氣確認「這真的沒用嗎」。Fowler 的態度很乾脆:刪掉它,別註解掉。 「以後可能會用到」?版本控制會幫你記得,需要時 git 撈得回來。
注意事項
⚠️ 搬移是高風險動作,更需要測試保護。 搬函式/欄位往往牽動很多呼叫端、改變可見性,比 Extract/Rename 更容易出錯。這正是 EP3「沒測試就別重構」最該被執行的場景。有 IDE 的 Move 重構功能就用,它比手動剪貼安全得多。
⚠️ 別為了「搬到完美位置」而癱瘓。 Fowler 那句「越困難的選擇往往越不重要」要傳達給聽眾:搬移的位置選擇常常沒有唯一解,糾結放 A 還是 B 通常代表兩邊差不多。重構是可逆的,先動、不對再搬回來,比卡在原地分析半天有用。
⚠️ Split Loop 和 Replace Loop with Pipeline 一定會有人跳出來談效能。 先準備好回應:(1) 多數情況下這點開銷可忽略;(2) 拆開/管線化後 code 更清楚,更容易發現「哪一段才是真瓶頸」;(3) 真有效能問題,那是重構之後針對量測結果做的獨立步驟。別讓「可能變慢」的假想,阻止你寫清楚的 code。
專家補充
💡 整章其實在操作軟體設計最古老的一對概念——高內聚(cohesion)、低耦合(coupling)。「把相關的搬到一起」=提高內聚;「Feature Envy 就搬走」=降低不該有的耦合。Fowler 沒有把這兩個詞掛在嘴邊,但每一招都在調這兩個旋鈕。在節目裡點明這層,能讓散落的手法收束成一個原理。
💡 Fowler 對 dead code 的態度(刪掉、別註解掉、靠 git 撈回)反映一個更大的工程價值觀:程式碼的閱讀成本是真實的成本。每一段「以防萬一留著」的死 code,都在向未來每一個讀者收稅。版本控制的存在,正是為了讓我們可以毫無心理負擔地刪東西。這條和《Pragmatic Programmer》「別留破窗」的精神一致。
💡 Replace Loop with Pipeline 背後是宣告式 vs 命令式的思維轉換:手寫迴圈是「告訴電腦每一步怎麼做」(命令式),管線是「描述你要什麼結果」(宣告式)。這個轉換是函數式程式設計影響主流語言的縮影——Fowler 第2版選 JavaScript,部分也是因為 JS 的 map/filter/reduce 讓這類重構講起來特別自然。
討論問題
🎙️ 開場鉤子:「你 code 裡有沒有一個函式,明明放在 A 類別,但它做的事八成都在動 B 類別的資料?那個函式其實一直想搬家——今天教你怎麼讓它搬對地方。」
🎙️ 自問自答:「一個函式到底該放哪個類別?我常常想很久。」——看它跟誰互動最多就放哪;而且如果你真的想很久,通常代表放哪都差不多(越難的選擇越不重要),先選一個,之後不對再搬。
🎙️ 帶走的一題:「找一個你 code 裡又長又雜的 for 迴圈,試著把它拆成『一個迴圈做一件事』,或乾脆改寫成 filter/map 的管線。讀讀看,是不是清楚多了?」
更大範圍關聯
- 高內聚低耦合:這對 1970 年代就提出的結構化設計原則,是整章的底層邏輯。搬移特性就是持續調整模組邊界、逼近「相關的在一起、無關的分開」的過程。
- 與封裝(EP4)的接力:先把資料封裝成物件(EP4),才有「該把哪些行為搬進這個物件」的問題(這集)。封裝畫出容器,搬移決定容器裡裝什麼。
- 與函數式思潮:Replace Loop with Pipeline 是 FP 對主流 OO 語言的滲透;它讓「資料轉換」這件事變得可組合、可讀。可接到更廣的「不可變資料 + 純轉換」設計風格。
- 與《Clean Code》函式章:「一個函式只做一件事」「一個迴圈只做一件事」是同一個單一職責直覺在不同尺度的展現,兩本互文(見 [軟體工程領域切分藍圖])。
錄製建議
- 建議時長:約 19-20 分鐘。配比:Move Function/Field(接 Feature Envy)約 6 分、Slide/Move Statements 約 3 分、Split Loop 約 4 分、Replace Loop with Pipeline 約 4 分、Remove Dead Code 約 2 分。
- 討論策略:開場用「函式放錯家」的畫面,把整章收束到「跟著互動走、追求高內聚低耦合」這條主軸。Split Loop 和 Pipeline 兩段一定要主動把「效能疑慮」提出來並回應,否則聽眾會分心去想。Remove Dead Code 當輕鬆的收尾,丟一句「刪掉它,git 會記得」很有記憶點。
- 反覆強調 Fowler 那句「越困難的選擇往往越不重要」——這句能治很多工程師的選擇癱瘓,很值得當金句送出去。