重構(gòu)讀書筆記——重新組織函數(shù)

Extract Method 提煉方法


動機(jī)

  • Extract Method 是最常用的重構(gòu)手法之一。
  • 函數(shù)過長或者需要注釋才能理解代碼的用途。
  • 增加代碼的清晰度和重用度。

做法

  1. 創(chuàng)造一個新函數(shù),更具函數(shù)的意圖來命名。(以它“做什么”來命名,而不是“怎樣做”)

即使你想要提煉的代碼非常簡單,例如只是一條消息或一個函數(shù)調(diào)用,只要新函數(shù)的名稱能夠以更好地方式昭示代碼意圖,你也應(yīng)該提煉它,但如果你想不出一個更有意義的名稱,就別動。

  1. 將提煉的代碼從源函數(shù)復(fù)制到目標(biāo)函數(shù)中。
  2. 檢查是否引用了“作用域限于源函數(shù)”的變量。(包括局部變量和源函數(shù)參數(shù))
  3. 檢測是否有“僅用于被提煉代碼段”的臨時變量,若有,則在目標(biāo)函數(shù)中聲明臨時變量。
  4. 檢查提煉代碼段,檢測局部變量的值被修改,如果有,則嘗試將其改為一個查詢(再提煉一個函數(shù)?),賦值給相關(guān)變量。
  5. 將提煉代碼中需要讀取的局部變量當(dāng)做參數(shù)傳給目標(biāo)函數(shù)。
  6. 編譯、替換。(刪除移入到提煉函數(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)。

做法

  1. 檢查函數(shù),確定不具多態(tài)性。

如果子類繼承了這個函數(shù),就不要將此函數(shù)內(nèi)聯(lián),因?yàn)樽宇悷o法覆寫一個根本不存在的函數(shù)。

  1. 找出函數(shù)所有調(diào)用點(diǎn)。
  2. 將調(diào)用點(diǎn)替換為本體。
  3. 編譯,測試。
  4. 刪除函數(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等.

做法

  1. 檢查賦值語句,確保等號右邊表達(dá)式?jīng)]有副作用。
  2. 確保變量只被賦值一次,可以用不可修改關(guān)鍵字來修飾(如java中的final),再編譯,看是否有報(bào)錯。
  3. 找到所有臨時變量引用點(diǎn),替換為表達(dá)式。
  4. 編譯,測試,刪除原臨時變量的聲明和賦值。

個人感覺這種方法,如果不是特殊需求,不是很有重構(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處理

做法

  1. 找出只被賦值一次的臨時變量。

如果變量賦值超過一次,考慮使用Split Temporary Variable分割成多個變量。

  1. 將臨時變量聲明為不可變類型。(確保變量只被賦值一次)
  2. 將變量右側(cè)的表達(dá)式提煉到一個單獨(dú)函數(shù)中。確保無副作用,不改變對象內(nèi)容。
  3. 編譯,測試。
  4. 對該變量使用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 來替換。

做法

  1. 聲明臨時變量,將復(fù)雜表達(dá)式中一部分動作賦值給臨時變量。
  2. 用臨時變量替換表達(dá)式中“運(yùn)算結(jié)果”的部分。
  3. 編譯,測試。
  4. 重復(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é)任,有可能引起代碼閱讀者糊涂。

做法

  1. 在待分解的臨時變量的聲明及其第一次被賦值處,修改器名稱。
  2. 新臨時變量聲明為不可變類型。
  3. 在第二次賦值之前,使用新名稱替換之前的引用點(diǎn)。
  4. 編譯測試。
  5. 重復(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ù))

做法

  1. 建立一個臨時變量,把待處理的參數(shù)賦值給它。
  2. 以“對參數(shù)的賦值”為界,將其后參數(shù)的引用點(diǎn)替換為臨時變量。
  3. 修改賦值語句。
  4. 編譯、測試。

注意值傳遞和引用傳遞

Replace Method With Method Object(以函數(shù)對象取代函數(shù))


你有一個大型函數(shù),對局部變量的使用使你無法使用Extend Method
將這個函數(shù)放進(jìn)一個單獨(dú)對象中,局部變量就成了對象內(nèi)的字段,可以在同一對象中,將這個大型函數(shù)分解成多個小函數(shù)。

動機(jī)

  • 函數(shù)過長,局部變量過多,Extract Method 比較困難。

做法

  1. 建立一個新類,根據(jù)函數(shù)的用途,為類命名。
  2. 建立一個函數(shù)原先對象的字段,將這個字段稱為源字段(聲明為不可變)。對原函數(shù)的臨時變量和參數(shù)創(chuàng)建字段,并保存。
  3. 建立一個構(gòu)造函數(shù),接收源對象和參數(shù)。
  4. 建立一個Compute()函數(shù)。
  5. 將原函數(shù)復(fù)制到Compute函數(shù)中,使用對象中的字段,替換原函數(shù)中的臨時變量。
  6. 編譯。
  7. 使用其他重構(gòu)方法,精簡函數(shù)。

Substitute Algorithm(替換算法)


動機(jī)

  • 有更簡潔、更高效、更清晰的算法。
  • 需要就算法不足以支持某些功能。

做法

  1. 算法過于復(fù)雜,可拆分成小算法,再處理。
  2. 測試,與原算法結(jié)果對比。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 《重構(gòu)》讀書筆記 總覽 第一部分 第一章從實(shí)例程序出發(fā),展示設(shè)計(jì)的缺陷,對其重構(gòu)可以了解重構(gòu)的過程和方法。 第二部...
    白樺葉閱讀 2,535評論 2 5
  • 如何實(shí)施重構(gòu) 稍微復(fù)雜的重構(gòu)過程,都是由一系列的基本重構(gòu)手法組成. 《重構(gòu)》一書中針對各種重構(gòu)場景,給出了大量的重...
    MagicBowen閱讀 4,466評論 0 3
  • 函數(shù)重構(gòu)幾乎都是源自于Long Methods。這導(dǎo)致了函數(shù)包含的信息過多,信息帶來的邏輯錯綜復(fù)雜。 1 Extr...
    hklbird閱讀 533評論 0 1
  • 閱讀《重構(gòu)》的筆記獻(xiàn)上。 重構(gòu)的定義 重構(gòu)是在不改變軟件可觀察行為的前提下改善其內(nèi)部結(jié)構(gòu)。 重構(gòu)的節(jié)奏 以微小的步...
    陳宇明閱讀 11,757評論 13 64
  • 懷著興奮和忐忑的心從北京到丹東的火車下來,叫了一輛車趕往丹東雙靈寺內(nèi)觀中心。路上和師父有一腔沒一腔的聊著,心里還掛...
    可樂先生要重啟閱讀 1,375評論 6 2

友情鏈接更多精彩內(nèi)容