[Book] 重構:改善既有程式的設計
Refactoring: Web Edition @ informit
Book Refactoring @ Gitlab
Chapter 6:第一組重構
Extract Function (提取函式)
時機
作者認為,提取函式的時間不是依照程式碼的長度、也不是只有在程式碼會被重複使用時,才來做提取函式的動作,而是「如果你必須費心查看一段程式碼才能了解它究竟在做什麼,你就應該把它拆出來,並且以它的目的來命名。」
"Separation between intention(what) and implementation."
做法
建立一個新函式,並且根據函式的目的來為它命名(根據 what 而不是 how 來命名)。
Inline Function(將函式內聯)
時機
「當程式碼本身的內容和函式名稱一樣清楚時」就不需要額外做內聯,因為多這一層反而增加認知負擔。
作法
將原本函式中的程式碼,直接放回執行它的地方,而不在包這一層函式。
Extract Variable(提取變數)
時機
當只看某個邏輯、運算式或判斷式沒辦法立即瞭解其意義時
做法
將比較複雜的邏輯命名,以讓其他開發者更容易理解
許多編輯器都有提供了 Refactor 的功能,方便開發者做到將程式提取成變數的動作。
VSCode 能將所選的程式碼抽取成變數:
Goland 能將所選的程式碼,以及相同的程式碼片段,同時抽取成變數:
Inline Variable(內聯變數)
時機
運算式或程式碼本身已經能清楚傳達訊息,不需要額外提取成變數,以避免額外負擔
做法
幫變數的程式碼貼回使用它的地方
Change Function Declaration(修改函式宣告式)
時機
發現函式的名稱不易理解時
做法
改善名稱的一個好方法是:「寫下註解來說明函式的用途,再把註解變成一個名稱」:
- 修改函式名稱:讓名稱能夠更清楚該函式做了什麼
- 修改函式參數:傳入整個物件或特定屬性
修改函式或變數名稱時,請善用 Editor/IDE 提供的 refactor 的功能(預設是按 F2),它會自動幫你修改所有參照到這個變數的地方。
如果我們希望將這個函式的名稱從 circum
改為 circumference
:
func circum(radius float64) float64 {
return 2 * math.Pi * radius
}
但因為使用這個函式的地方非常多,可能沒辦法一次全部取代,所以我們暫時先同時保留兩個函式:
func circum(radius float64) float64 {
+ return circum(radius)
+}
+
+func circumference(radius float64) float64 {
return 2 * math.Pi * radius
}
在有使用到這個函式的地方,陸續進行替換,知道全部替換完畢後,在移除舊有的函式。
思考
在傳遞函式參數的時候,經常會碰到要傳遞整個物件或只有使用到屬 性。當傳遞整個物件時,會讓函式和這個物件的介面耦合,但卻可以輕鬆的讀取該物件中的其他屬性,當未來邏輯改變時,不需要修改這個函式的任何呼叫方,並可以增加函式的封裝性。
Rename Variable(更改變數名稱)
如果要修改「常數」名稱,和修改函式名稱一樣,一個好用的技巧是先同時新舊兩個變數名稱,逐步替換完畢後,再把舊的變數刪除。
假設我們希望把 cpyNm
改成 companyName
:
// 這是原本的變數名稱
const cpyNm = 'Acme Gooseberries';
我們先建立新的變數,並將舊的變數參照到新的變數:
// 這是希望修改後的變數名稱
const companyName = 'Acme Gooseberries';
const cpyNm = companyName;
Combine Functions into Transform(將函式組成轉換函式)
動機
我們經常需要在取得原始資料後,透過一些前處理來取得 derived data,而許多的 derived state 或 derived data 都是根據相同的邏輯而產生。作者喜歡把所有這類的計算邏輯放在一起,以便在固定的地方尋找與修改他們。
做法
使用「資料轉換函式」,用它接受資料,並計算所有的衍生值。
命名上,如果轉換邏輯產生的是相同東西,但是有額外的資訊時,作者習慣用 enrich
,例如,enrichReading
;但如果會產生的是不一樣的東西時,則習慣用 transform
,例如,transformReading
。
針對還沒轉換前的資料,作者習慣加上 raw
, 例如 rawReading
。
如果來源資料會被程式碼所更新,使用類別比較好,才能確保每次拿到的都是最新且正確的資料。
其他
- Encapsulate Variable(封裝變數)
- Introduce Parameter Object(使用參數物件)
- Combine Functions into Class(將函式移入類別)
- Split Phase(拆成不同階段):當遇到處理兩件不同事情的一段程式碼時
Chapter 08: 移動功能
- Demo with Github Copilot
移動函式(Move Function)
有時你會很難決定函式最好的位置, 但是通常選擇越困難,這件事就越不重要。