Extract Method 提煉方法
動機(jī)
- Extract Method 是最常用的重構(gòu)手法之一。
- 函數(shù)過長或者需要注釋才能理解代碼的用途。
- 增加代碼的清晰度和重用度。
做法
- 創(chuàng)造一個新函數(shù),更具函數(shù)的意圖來命名。(以它“做什么”來命名,而不是“怎樣做”)
即使你想要提煉的代碼非常簡單,例如只是一條消息或一個函數(shù)調(diào)用,只要新函數(shù)的名稱能夠以更好地方式昭示代碼意圖,你也應(yīng)該提煉它,但如果你想不出一個更有意義的名稱,就別動。
- 將提煉的代碼從源函數(shù)復(fù)制到目標(biāo)函數(shù)中。
- 檢查是否引用了“作用域限于源函數(shù)”的變量。(包括局部變量和源函數(shù)參數(shù))
- 檢測是否有“僅用于被提煉代碼段”的臨時變量,若有,則在目標(biāo)函數(shù)中聲明臨時變量。
- 檢查提煉代碼段,檢測局部變量的值被修改,如果有,則嘗試將其改為一個查詢(再提煉一個函數(shù)?),賦值給相關(guān)變量。
- 將提煉代碼中需要讀取的局部變量當(dāng)做參數(shù)傳給目標(biāo)函數(shù)。
- 編譯、替換。(刪除移入到提煉函數(shù)內(nèi)的局部變量在源函數(shù)中的聲明)
Inline Method (內(nèi)聯(lián)函數(shù))
一個函數(shù)的本體與名稱同樣清楚易懂,在函數(shù)調(diào)用點(diǎn)插入函數(shù)本體,然后移除該函數(shù)。
重構(gòu)前
int GetRating()
{
return (MoreThanFiveLateDeliveries()) ? 2 : 1;
}
bool MoreThanFiveLateDeliveries()
{
return _numberOfLateDeliveries > 5;
}
重構(gòu)后
int GetRating()
{
return (_numberOfLateDeliveries > 5)? 2 : 1;
}
動機(jī)
- 函數(shù)過于簡單,內(nèi)部代碼與函數(shù)名一樣清晰。
- 組織多個不合理的函數(shù)時,可以先將函數(shù)內(nèi)聯(lián)到一個函數(shù)中,然后再使用提煉函數(shù)等方法進(jìn)行重構(gòu)。
做法
- 檢查函數(shù),確定不具多態(tài)性。
如果子類繼承了這個函數(shù),就不要將此函數(shù)內(nèi)聯(lián),因?yàn)樽宇悷o法覆寫一個根本不存在的函數(shù)。
- 找出函數(shù)所有調(diào)用點(diǎn)。
- 將調(diào)用點(diǎn)替換為本體。
- 編譯,測試。
- 刪除函數(shù)定義。
Inline Temp (內(nèi)聯(lián)臨時變量)
一個臨時變量,只被賦值一次,并且妨礙了其他重構(gòu)手法。
將所有對該變量的引用動作,替換為對它賦值的那個表達(dá)式自身。
重構(gòu)前
double basePrice = anOrder.basePrice();
return (basePrice > 100);
重構(gòu)后
return (anOrder.basePrice() > 100);
動機(jī)
- 多半是使用在別的重構(gòu)手法中。如Replace Temp with Query 或者Inline Temp等.
做法
- 檢查賦值語句,確保等號右邊表達(dá)式?jīng)]有副作用。
- 確保變量只被賦值一次,可以用不可修改關(guān)鍵字來修飾(如java中的final),再編譯,看是否有報(bào)錯。
- 找到所有臨時變量引用點(diǎn),替換為表達(dá)式。
- 編譯,測試,刪除原臨時變量的聲明和賦值。
個人感覺這種方法,如果不是特殊需求,不是很有重構(gòu)的必要,不能為了重構(gòu)而重構(gòu)。
Replace Temp with Query(以查詢?nèi)〈R時變量)
程序以一個臨時變量保存某一表達(dá)式的運(yùn)算結(jié)果。
將這個表達(dá)式提煉到一個獨(dú)立函數(shù)中。將這個臨時變量的所有引用點(diǎn)替換為對新函數(shù)的調(diào)用。此后,新函數(shù)就可被其他函數(shù)調(diào)用
重構(gòu)前
double basePrice = _quantity * _itemPrice;
if(basePrice > 1000)
return basePrice * 0.95;
else
return basePrice * 0.98;
重構(gòu)后
if(BasePrice() > 1000)
return BasePrice() * 0.95;
else
return BasePrice() * 0.98;
...
double BasePrice()
{
return _quantity * _itemPrice;
}
動機(jī)
- 臨時變量只能在所屬函數(shù)內(nèi)使用,可能會驅(qū)使你寫出更長的函數(shù),因?yàn)橹挥羞@樣才能訪問到需要的臨時變量(比如多個函數(shù)都需要使用時)。寫成一個查詢,則同一個尅中的所有函數(shù)都可獲取這份信息。
- 經(jīng)常用在Extrct Method 中。
- 如果一個變量被賦值多次,可能先需要使用Split Temporary Variable或者 Separate Query from Modifier處理
做法
- 找出只被賦值一次的臨時變量。
如果變量賦值超過一次,考慮使用Split Temporary Variable分割成多個變量。
- 將臨時變量聲明為不可變類型。(確保變量只被賦值一次)
- 將變量右側(cè)的表達(dá)式提煉到一個單獨(dú)函數(shù)中。確保無副作用,不改變對象內(nèi)容。
- 編譯,測試。
- 對該變量使用Inline Temp。
頻繁的函數(shù)調(diào)用可能會有一點(diǎn)點(diǎn)性能問題,但是清晰的邏輯更容易維護(hù)代碼,找到更好的優(yōu)化方案。如果性能開銷確實(shí)很大,則可使用變得方法。
Introduce Explaining Variable(引入解釋性變量)
將一個復(fù)雜的表達(dá)式(或其中一部分)的結(jié)果放進(jìn)一個臨時變量,以此變量名來解釋表達(dá)式的用途
重構(gòu)前
if((platform.ToUpperCase().IndexOf("Mac") > -1
&& (browser.ToUpperCase().IndexOf("IE") > -1)
&& WasInitlized()
&& Resize > 0)
{
//do something
}
重構(gòu)后
bool isMacOs = platform.ToUpperCase().IndexOf("Mac") > -1 ;
bool isIEBrowser = browser.ToUpperCase().IndexOf("IE") > -1);
bool wasResized = resize > 0;
if(isMacOs && isIEBrowser
&& WasInitialized() && wasResized)
{
//do something
}
動機(jī)
- 表達(dá)式復(fù)雜難以閱讀,可以使用該方法分解,便于管理。
- 較長的算法中,可以使用該方法解釋步驟的意義。
- 可以使用Extract Method 來替換。
做法
- 聲明臨時變量,將復(fù)雜表達(dá)式中一部分動作賦值給臨時變量。
- 用臨時變量替換表達(dá)式中“運(yùn)算結(jié)果”的部分。
- 編譯,測試。
- 重復(fù)上述操作,處理表達(dá)式其他部分。
Extract Method在很多時候比該方法更容易實(shí)現(xiàn),在Extract Method比較麻煩的時候再考慮該方法。
Split Temporary Variable(分解臨時變量)
程序中某個臨時變量被賦值超過一次,并且它既不是循環(huán)變量,也不是用于收集計(jì)算結(jié)果。
針對每次賦值,創(chuàng)造一個獨(dú)立、對應(yīng)的臨時變量。
重構(gòu)前
double temp = 2 * (_height * _width);
Print(temp);
temp = _height * _width;
Print(temp);
重構(gòu)后
double perimeter = 2 * (_height * _width);
Print(perimeter);
double area = _height * _width;
Print(area);
動機(jī)
- 除“循環(huán)變量”和“結(jié)果收集變量”,同一變量被賦值多次,會使變量承擔(dān)多個責(zé)任,有可能引起代碼閱讀者糊涂。
做法
- 在待分解的臨時變量的聲明及其第一次被賦值處,修改器名稱。
- 新臨時變量聲明為不可變類型。
- 在第二次賦值之前,使用新名稱替換之前的引用點(diǎn)。
- 編譯測試。
- 重復(fù)上述操作。
Remove Assignments to Parameters(移除對參數(shù)的賦值)
代碼對一個參數(shù)進(jìn)行賦值。
以一個臨時變量取代該函數(shù)的位置
重構(gòu)前
int Discount(int inputVal, int quantity, int yearToDate)
{
if(inputVal > 50) inputVal -= 2;
//do something
}
重構(gòu)后
int Discount(int inputVal, int quantity, int yearToDate)
{
int result = inputVal;
if(inputVal > 50) result -= 2;
//do something
}
動機(jī)
- 可能會降低代碼清晰度。
- 復(fù)雜的參數(shù)對象,可能會導(dǎo)致對原參數(shù)的引用。
- 輸出參數(shù)除外。(如 C#中 out 修飾的參數(shù))
做法
- 建立一個臨時變量,把待處理的參數(shù)賦值給它。
- 以“對參數(shù)的賦值”為界,將其后參數(shù)的引用點(diǎn)替換為臨時變量。
- 修改賦值語句。
- 編譯、測試。
注意值傳遞和引用傳遞
Replace Method With Method Object(以函數(shù)對象取代函數(shù))
你有一個大型函數(shù),對局部變量的使用使你無法使用Extend Method
將這個函數(shù)放進(jìn)一個單獨(dú)對象中,局部變量就成了對象內(nèi)的字段,可以在同一對象中,將這個大型函數(shù)分解成多個小函數(shù)。

動機(jī)
- 函數(shù)過長,局部變量過多,Extract Method 比較困難。
做法
- 建立一個新類,根據(jù)函數(shù)的用途,為類命名。
- 建立一個函數(shù)原先對象的字段,將這個字段稱為源字段(聲明為不可變)。對原函數(shù)的臨時變量和參數(shù)創(chuàng)建字段,并保存。
- 建立一個構(gòu)造函數(shù),接收源對象和參數(shù)。
- 建立一個Compute()函數(shù)。
- 將原函數(shù)復(fù)制到Compute函數(shù)中,使用對象中的字段,替換原函數(shù)中的臨時變量。
- 編譯。
- 使用其他重構(gòu)方法,精簡函數(shù)。
Substitute Algorithm(替換算法)
動機(jī)
- 有更簡潔、更高效、更清晰的算法。
- 需要就算法不足以支持某些功能。
做法
- 算法過于復(fù)雜,可拆分成小算法,再處理。
- 測試,與原算法結(jié)果對比。