《重構》讀書筆記——代碼的壞味道(重複代碼)

重複代碼
如果在一個以上的地方看見相同的程序結構,那麼合而爲一,是比較好的選擇
1、同一個類的兩個函數含有相同的表達式——使用“提煉函數”的解決辦法
2、兩個互爲兄弟的子類內含有相同的表達式——先使用“提煉函數”,然後再對被提煉出來的代碼使用“函數上移”,將它推入超類。如果代碼只是類似而不是相同,那麼就使用“提煉函數”,將相似的和差異部分分隔開,構成單獨的函數。然後你可以使用“塑造模版函數”獲得一個模版方法設計模式,如果有些函數以不同的算法做相同的事情,可以選擇較清晰的一個,使用“替換算法”將其他替換掉。
3、如果兩個毫不相干的類出現重複代碼,該考慮使用“提煉類”,將重複的代碼提煉到一個獨立的類中,然後在另一個類使用這個新類

提煉函數
1、創建一個新函數,根據意圖對它命名
2、將提煉出的代碼從源函數複製入目標函數
3、仔細檢測提煉的代碼,看其中是否有“作用於僅限於源函數”的變量(包括局部變量和源函數參數)
4、檢查任何局部變量的值是否被改變,如果被改變看看能否作爲一個查詢,然後將結果返回。如果很難這麼做,
那麼使用“分解臨時變量”或者“用查詢代替臨時變量”來解決
5、將提煉代碼段中需要讀取的局部變量,當作參數傳給目標函數
6、處理完局部變量,進行重新編譯
7、在源函數中,將提煉代碼段替換爲對目標函數的調用

分解臨時變量:如果一個臨時變量被賦值多次,那麼說明它承擔了多個責任(循環變量和結果收集變量除外),同一個臨時變量承擔多個責任會讓人糊塗,所以需要分解成多個臨時變量
1、在待分解臨時變量和第一次被賦值處,修改其名稱(注意,如果是i=i+某表達式的形式則不要分解它,一般爲結果收集變量,通常作用是累加,字符串接合,寫入流或者集合添加元素)
2、將新的臨時變量聲明爲final
3、以該臨時變量的第二次賦值動作爲界,修改此前所有對臨時變量的引用點,讓他們引用新的臨時變量
4、在第二次賦值處,重新聲明原先那個臨時變量
5、編譯,測試
6、逐次重複上述過程,每次都在聲明處對臨時變量改名,並修改下次賦值之前的引用點

用查詢代替臨時變量:將臨時變量變爲一次函數查詢,可以簡化函數,編寫更清晰的代碼
1、找出只被賦值一次的臨時變量(如果被多次賦值,考慮使用“分解臨時變量”
2、將臨時變量聲明爲final
3、編譯(確保只被賦值一次)
4、將“對該臨時變量的賦值表達式”提煉到一個函數當中(首先聲明爲private,然後再放寬限制。確保提煉的函數沒有副作用,即不修改任何對象的內容,否則使用“將查詢函數和修改函數分離”
5、編譯,測試
6、在該臨時變量上實施“內聯臨時變量”

將查詢函數和修改函數分離:某個函數既返回對象狀態值,有修改對象狀態,那麼建立兩個不同的函數,其中一個負責查詢,另一個負責修改
1、新建一個查詢函數,令它的返回值與原函數相同(如果返回臨時變量,那麼找出臨時變量的位置)
2、修改原函數,令它調用查詢函數,並返回獲得的結果(每個return語句都應該返回return newQuery())
3、編譯測試
4、將調用原函數的代碼改爲調用查詢函數,然後,在調用查詢函數的那一行之前,加上對原函數的調用,每次修改後,編譯並測試
5、將原函數的返回值改爲void,並刪除所有的return語句。

內聯臨時變量:你有一個臨時變量只被簡單表達式賦值一次,而它妨礙了其他重構手法,將所有對該變量的引用動作,替換爲對它賦值的表達式本身。一般和用查詢代替臨時變量一起使用
1、檢查給臨時變量賦值的語句,確保等號右邊的表達式沒有副作用。
2、將這個臨時變量聲明爲final,然後編譯(確保這個臨時變量只被賦值一次)
3、找到所有的臨時變量引用點,將其替換爲表達式。
4、每次修改後,編譯並測試。
5、修改完所有的引用點後,編譯,測試。

函數上移:對於在各個子類中某個函數完全相同的情況,將該函數移至父類
1、檢查待提升的函數,確定它是完全一致的(如果不一致可使用“替換算法”讓它們一致)
2、如果待提升的函數簽名不同,將那些簽名都改成你想在超類中使用的簽名
3、在超類中新建函數,將函數內容複製到其中,做適當調整然後編譯(如果待提升的函數使用了一個子類的字段,可以使用“字段上移”或者使用“自封裝字段”,然後在超類中把取值函數聲明爲抽象)
4、逐一移除所有的子類函數,每次移除都要編譯測試
5、觀察函數調用者,是看看是否可以改爲使用超類類型的對象。

替換算法:把某個算法替換爲另一個更清晰的算法
1、準備好另一個算法,讓它通過編譯
2、針對現有測試,執行上述新算法,如果結果和原本結果相同,重構結束
3、如果結果不相同,在測試和調試過程中,以舊算法爲比較參照標準

字段上移:兩個子類擁有相同的字段,將該字段移植超類
1、針對待提升的字段,檢查它們的所有被使用點,確認它們以相同的方式被使用
2、如果這些字段名稱不同,先將他們改名,改作你超類裏面想取的名字
3、編譯,測試
4、在超類中新建一個字段(如果是private的,則聲明爲protected)
5、移除子類字段,編譯測試
6、考慮對超類的新建字段使用“自封裝字段”

“自封裝字段”:你直接訪問一個字段,但字段之間的耦合關係逐漸變嘚笨拙,爲這個字段設置get/set函數,並且只以這些函數來訪問字段。
1、爲待封裝的字段建立get/set函數
2、找出該字段的所有引用點,將它們全部改爲調用get/set函數(可以將該字段改名,讓編譯器幫你找出所有引用點)
3、將字段聲明爲private
4、複查,確保已經找出所有引用點
5、編譯測試

“塑造模版函數”:你有一些子類,其中相應的某些函數具有相同順序執行的操作,但操作細節有所不同,將這些操作分別放進獨立函數中,並保持它們相同的簽名,於是原函數也相同,將原函數上移至超類。
1、在各個子類中分解目標函數,是分解後的各個函數要不完全相同,要不完全不同
2、運用“函數上移”將各個子類內完全相同的函數上移至超類
3、對於剩下的完全不同的函數,實施“函數改名”,是所有的這些函數簽名完全相同
4、修改上述簽名後,編譯並測試
5、運用“函數上移”將所有原函數逐一上移到超類,在超類中將哪些代表不同操作的函數定義爲抽象函數
6、編譯測試
7、移除其他子類中的原函數,逐一編譯測試

“函數改名”:函數的名稱未能揭示出函數的用途,修改函數名稱
1、檢查函數名是否被超類或者子類實現過,如果是,則針對每份實現分別進行一下步驟:
2、聲明一個新函數,將它命名爲你想要的新名稱。將舊函數的代碼複製到新函數,並適當調整
3、編譯
4、修改舊函數,令它將調用轉發給新函數。
5、編譯,測試
6、找到所有舊函數的引用點,修改它們,令它們改用新函數,每次修改必須編譯測試
7、刪除舊函數
8、編譯測試

提煉類:某個類做了應該由兩個類做的事情,建立一個新類,將相關的字段和函數從舊類搬到新類
1、決定如何分解類所承擔的責任
2、建立一個新類,用以表現從舊類中分離出來的責任(如果舊類剩下的責任與名稱不符合,爲舊類更名)
3、建立“從舊類訪問新類”的連接關係
4、對於你想搬運的每一個字段,用“搬運字段”來搬運它
5、每次搬運後,編譯測試
6、使用“搬運函數”將必要的函數移至新類,先移動比較低層的函數(也就是被其他調用多於調用其他的函數),再搬運高層函數
7、每次搬運之後,編譯測試
8、檢查,精簡每個類的接口
9、決定是否需要公開新類,如果需要公開,就要決定讓它成爲引用對象還是不可變值對象

搬運字段:你的程序中,某個字段被其所在的類之外的另一個類更多的用到,在目標類中新建一個字段,修改源字段的所有用戶,令它們改用新字段
1、如果字段的訪問級是public,使用“封裝字段”將它封裝起來(如果你有可能移動哪些頻繁訪問的字段或者有很多函數訪問,先使用“自封裝字段”
2、編譯,測試
3、在目標類中建立與源字段相同的字段,並同時建立相應的設值/取值函數
4、編譯目標類
5、決定如何在源對象中引用目標對象
6、刪除源字段
7、將所有對源字段的引用替換爲對某個目標函數的調用
8、編譯測試

搬移函數:在該函數最常引用的類建立一個有着類似行爲的新函數。將舊函數變成一個單純的委託函數或者將舊函數移除。
1、檢查源類中被源函數所使用的一切特性(包括字段和函數),考慮它們是否也該被搬移(如果某個特性只被你打算搬移的函數用到,就該把它們一併搬移)
2、檢查源類的子類和超類,看看是否有該函數的其他聲明(如果出現其他聲明,你無法搬移,除非目標類也表現出同樣的多態性)
3、在目標類中聲明這個函數(你可以爲此函數重新取一個名字)
4、將源函數的代碼複製到目標函數中。調整後者,使其能在新類中正常運行(如果目標函數使用了源類的特性,你得決定如何從目標函數引用源對象,如果沒有相應的引用機制,則把源對象的引用當作參數傳遞)
5、編譯目標類
6、決定如何從源函數正確引用目標對象(可能會有一個現成的字段或者函數幫助你取得目標對象,如果沒有,就看能否輕鬆建立一個這樣的函數,如果還是不行,就在源類中新建一個字段來保存目標對象,這樣可能是一個永久性修改,但你也可以讓它是暫時的)
7、修改源函數,使之成爲一個純委託函數
8、編譯測試
9、決定是否刪除函數,或者將它作爲一個委託函數保留下來(如果你經常要在源對象中引用目標函數,那麼作爲委託函數保留會比較簡單)
10、如果要移除源函數,請將源類中對源函數的所有調用,替換爲對目標函數的調用
11、編譯測試

封裝字段:將原來的public字段聲明爲private,並提供相應的訪問函數
1、爲public字段提供set/get函數
2、找到這個類以外使用該字段的所有地點。如果客戶只是讀取了該字段,就把引用替換爲對取值函數的調用;如果用戶修改了該字段,就將此引用替換爲對設值函數的調用
3、每次修改後,編譯測試
4、將字段的所有用戶修改完畢後,把字段聲明爲private
5、編譯測試
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章