重構(Refactoring)技巧

 
重構(Refactoring)技巧  
本文簡要整理重構方法的讀書筆記及個人在做Code Review過程中,對程序代碼常用的一些重構策略。通過適當的重構代碼,的確可以顯著提高代碼的質量,令人賞心悅目。毫無疑問,這些重構策略均來自於Martin Fowler《重構-改善既有代碼的設計》,只是如何在實際項目中靈活運用而已。(注:本文重構策略的名稱及其大部分內容來自《重構-改善既有代碼的設計》一書,Martin Fowler 著,侯捷等譯)。
先看看重構的定義吧:
1Refactoring means rewriting existing source code with the intent of improving its design rather than changing its external behavior. The focus of refactoring is on the structure of the source code, changing the design to make the code easier to understand, maintain, and modify. 來自Borland Together提供的文檔,覺得這個定義很清晰明瞭。
2)重構是這樣一個過程:在不改變代碼外在行爲的前提下,對代碼做出修改,已改進程序的內部結構。-來自Martin Fowler的定義。
不過,我一般使用一些重構的工具,如ReSharper for VS.Net v1.0Borland Together for VS.Net v2.0,這些重構工具可以幫助你做很多事情,可以簡化你許多工作,同時也可以避免出現一些錯誤。關於ReSharper for VS.Net v1.0的簡單介紹,可以參考Rickie 以前的一篇Posting體驗ReSharper V1.0 for VS.Net 2003 - Part IVS.Net 2005已經內置了重構功能。不過,目前這些重構工具還遠遠不能涵蓋各種重構方法,有總比沒有好了。, 體驗ReSharper V1.0 for VS.Net 2003 - Part II】。另外
因此,掌握必要的重構技巧逐步成爲對程序員基本的要求,重要的是在掌握這些技巧後,也有助於類庫初期設計的質量,避免或減少代碼的壞味道(bad smell)
 
一、代碼壞味道(Bad Smell in Codes)及其重構策略
1.儘量消除重複的代碼,將它們合而爲一
根據重複的代碼出現在不同的地方,分別採取不同的重構的策略:
在同一個Class的不同地方:通過採用重構工具提供的Extract Method功能提煉出重複的代碼,然後在這些地方調用上述提煉出方法。
在不同Subclasses中:通過Extract Method提煉出重複的代碼,然後通過Pull Up Method將該方法移動到上級的Super class內。
在沒有關係的Classes中:通過對其中一個使用Extract Class將重複的代碼提煉到一個新類中,然後在另一個Class中調用生成的新類,消除重複的代碼。
 
2.拆解過長的函數
過長的函數在我們的日常代碼中經常可見,在C#中常通過#region #endregion區隔爲不同的功能區域。
重構策略:通過Extract Method將過長的函數按照功能的不同進行適當拆解爲小的函數,並且給這些小函數一個好名字。通過名字來了解函數提供的功能,提高代碼的理解性。
 
3.拆解過大的類
過大的類也經常見到,特別是類中含有大量的成員變量。
重構策略:通過Extract Class將一些相關成員變量移植到新的Class中,如Employee類,一般會包含有聯繫方式的相關屬性(電話, Mobile,地址,Zip等等),則可以將這些移植到新的EmployeeContact類中。
 
4.過長的參數列
過長的參數列的主要問題是難以理解,並且難以維護。如果要增加新的參數或者刪除某一參數,易造成參數前後不一致。
重構策略:如果可以通過向已存在的對象查詢獲取參數,則可通過Replace Parameter with Method,移除參數列,通過在函數內部向上述已存在的對象查詢來獲取參數。
如果參數列中若干參數是已存在對象的屬性,則可通過Preserve Whole Object將這些參賽替換爲一個完整對象,這樣不僅提高代碼的可讀性,同時已易於代碼今後的維護。
另外,還可以將若干不相關的參數,使用Introduce Parameter Object來創建一個新的參數類。不過,我個人覺得如果這些情況過多的話,會產生很多莫名其妙的參數類了,反而降低代碼的可讀性。
 
個人覺得前面4種壞味道比較顯而易見,也比較容易處理。

 

現象:當某個Class因爲外部條件的變化或者客戶提出新的功能要求等時,每次修改要求我們更新Class中不同的方法。不過這種情況只有在事後才能覺察到,因爲修改都是在事後發生的麼(廢話)。
重構策略:將每次因同一條件變化,而需要同時修改的若干方法通過Extract Class將它們提煉到一個新Class中。實現目標是:每次變化需要修改的方法都在單一的Class中,並且這個新的Class內所有的方法都應該與這個變化相關。
 
6Shotgun Surgery(霰彈式修改)
現象:當外部條件發生變化時,每次需要修改多個Class來適應這些變化,影響到很多地方。就像霰彈一樣,發散到多個地方。
重構策略:使用Move MethodMove FieldClass中需要修改的方法及成員變量移植到同一個Class中。如果沒有合適的Class,則創建一個新Class。實現目標是,將需要修改的地方集中到一個Class中進行處理。
 
比較Divergent Change(發散式變化)和Shotgun Surgery(霰彈式修改):
前者指一個Class受到多種外部變化的影響。而後者指一種變化需要影響到多個Class需要修改。都是需要修理的對象。
 
7Feature Envy(依戀情結)
現象:Class中某些方法“身在曹營心在漢”,沒有安心使用Class中的成員變量,而需要大量訪問另外Class中的成員變量。這樣就違反了對象技術的基本定義:將數據和操作行爲(方法)包裝在一起。
重構策略:使用Move Method將這些方法移動到對應的Class中,以化解其“相思之苦”,讓其牽手。
 
8Data Clumps(數據泥團)
現象:指一些相同數據項目(Data Items),如Class成員變量和方法中參數列表等,在多個Class中多次出現,並且這些數據項目有其內在的聯繫。
重構策略:通過使用Introduce Parameter Object(創建新的參數對象取代這些參數)或Preserve Whole Object(使用已存在的對象取代這些參數),實現使用對象代替Class成員變量和方法中參數列表,清除數據泥團,使代碼簡潔,也提高維護性和易讀性。
 
9Primitive Obsession(基本型偏執狂)
現象:在Class中看到大量的基本型數據項目(Data Item),如Employee類中有大量的數據成員,Employee#, FirstName, MiddleName, LastName, Address, State, City, Street, Zip, OfficePhone, CellPhone, Email……等等。
重構策略:使用Extract Class(提煉新類)或Preserve Whole Object(使用已存在的對象取代這些參數),實現使用對象代替基本型數據項目(Data Item)。如上述Employee類中就可分別提煉出EmployeeNameEmployeeContact兩個新類。
 
10Switch StatementsSwitch語句)
現象:同樣的Switch語句出現在不同的方法或不同的Class中,這樣當需要增加新的CASE分支或者修改CASE分支內語句時,就必須找到所有的地方,然後進行修改。這樣,就比較麻煩了。
重構策略:(1)首先採用Extract MethodSwitch語句提煉到一個獨立的函數。
(2)然後以Move Method搬移到需要多態性(Polymorphism)的Superclass裏面或者是構建一個新的Superclass
(3)進一步使用Replace Type Code with Subclasses或者Replace Type Code with State/Strategy。這步就比較麻煩些,不過記住如下基本規則:這裏一般有3Class分別爲Source ClassSuperclassSubclass
Source Class
l         使用Self Encapsulate Field,將Type Code成員變量封裝起來,也就是建立對應的Setter/Getter函數。
l         Source Class中增加一個Superclass類型的成員變量,用來存放Subclass實例對象。
l         Source Class中的Getter函數,通過調用SuperclassAbstract Query函數來完成。
l         Source Class中的Setter函數,通過調用Superclass中的Static工廠化方法來獲取合適的Subclass實例對象。
 
Superclass
新建的一個Class(注:就是上面通過Move Method搬移生成的Superclass),根據Type Code的用途命名該Class,作爲Superclass
l         Superclass中建立一個Abstract Query函數,用來獲取SubclassType Code
l         Superclass中創建Static工廠化方法生產對應的Subclass對象,這裏會存在一個Switch語句(不要再動腦筋來重構這個Switch語句了,這個Switch語句不會在多處重複存在,並且這裏用於決定創建何種Subclass對象,這是完全可以接受的)。
 
Subclass
l         根據每一個Switch/Type分支,建立對應的Subclass,並且Subclass的命名可以參考Switch/Type分支的命名。
l         在每一個Subclass中重載SuperclassAbstract Query函數,返回特定的Type Code
(4)現在Superclass仍然存在Switch分支,是時候輪到Replace Conditional with Polymorphism上場了。具體而言,就是在每一個Subclass中創建重載方法(注:該方法是Superclass中含有Switch語句的方法),並將SuperclassSwitch語句對應的Case分支剪切過來。最後將Superclass中該方法初象化Abstract,並清除Switch語句及其所有的Case分支。
這樣就完成了整個重構過程,這個比較麻煩。
 
注:並不是一看到Switch語句及CASE分支,就馬上/偏執狂採用上述重構策略進行重構,畫蛇添足或喫虧不討好(個人觀點)。一般而言,只有看到多處出現相同的Switch語句時,才應該考慮進行重構。
 
11Parallel Inheritance Hierarchies(平行繼承體系)
現象:爲某個class增加一個subclass時,也必須爲另一個class相應增加一個subclass。重構策略:在一個class繼承體系的對象中引用(refer to)另一個class繼承體系的對象,然後運用Move MethodMove Field將被引用class中的一些方法和成員變量遷移宿主class中,消除被引用class的繼承體系(注:這種平行繼承體系好象比較少見也)。
 
12Lazy Class(冗贅類)
現象:某一些class由於種種原因,現在已經不再承擔足夠責任,有些多餘了。如同國有企業冗餘人員一樣,需要下崗了。
重構策略:通過Collapse Hierarchy,將這些冗餘的class合併到superclasssubclass中,或者通過Inline Class(與Extract Class相反),將這些冗餘class中的所有Method/Field遷移到其他相關的class中。
 
13Speculative Generality(誇誇其談未來性)
現象:系統中出現一些無用的abstract class,或者非必要的delegation(委託),或者多餘的參數等等。
重構策略:分別使用Collapse Hierarchy合併abstract class,使用Inline Class移除非必要的delegation,使用Remove Parameter刪除多餘的參數。
 
14Temporary Field(令人迷惑的暫時值域)
現象:class中存在一些Field,這些Field只在某種非常特定的情況下需要。
重構策略:通過Extract Class將這些孤獨的Field及其相關的Method移植的一些新的Class中。提煉出來的新Class可能沒有任何抽象意義,只是提供Method的調用,這些新Class一般稱爲Method Object
 
15Message Chains(過度耦合的消息鏈)
現象:向一個對象請求另一個對象,然後再向後者請求另一個對象,……,這就是Message Chain,意味着Message Chain中任何改變,將導致Client端不得不修改。
重構策略:通過Hide Delegate(隱藏委託關係)消除Message Chain,具體做法是在Message Chain的任何地方通過Extract Method建立一個簡單委託(Delegation)函數,來減少耦合(Coupling)。
 
16Middle Man(中間轉手人)
現象:過度運用delegation,某個/某些Class接口有一半的函數都委託給其他class,這樣就是過度delegation
重構策略:運用Remove Middle Man,移除簡單的委託動作(也就是移除委託函數),讓client直接調用delegate受託對象。和上面的Hide Delegate(隱藏委託關係)剛好相反的過程。
 
由於系統在不斷的變化和調整,因此[合適的隱藏程度]這個尺度也在相應的變化,Hide DelegateRemove Middle Man重構策略可以系統適應這種變化。
 
另外,可保留一部分委託關係(delegation),同時也讓Client也直接使用delegate受託對象。
 
17Inappropriate Intimacy(狎暱關係)
現象:兩個Class過分親密,彼此總是希望瞭解對方的private成分。
重構策略:可以採用Move MethodMove Field來幫助他們劃清界限,減少他們之間親密行爲。或者運用Change Bidirectional Association to Unidirectional,將雙向關聯改爲單向,降低Class之間過多的依存性(inter-dependencies)。或者通過Extract Class將兩個Class之間的共同點移植到一個新的Class中。
 
18Alternative Classes with Different Interfaces(異曲同工的類)
現象:兩個函數做相同的事情,卻有不同的signature
重構策略:使用Rename Method,根據他們的用途來重命名。另外,可以適當運用Move Method遷移某些行爲,使Classes的接口保持一致。
 
19Incomplete Library Class(不完美的程序庫類)
現象:Library Class(類庫)設計不是很完美,我們需要添加額外的方法。
重構策略:如果可以修改Library ClassSource Code,直接修改最好。如果無法直接修改Library Class,並且只想修改Library Class內的一兩個函數,可以採用Introduce Foreign Method策略:在Client Class中建立一個函數,以外加函數的方式來實現一項新功能(一般而言,以server class實例作爲該函數的第一個參數)。
 
如果需要建立大量的額外函數,可應該採用Introduce Local Extension:建立一個新class,使它包含額外函數,並且這個class或者繼承或者wrap(包裝)source class
 
20Data Class(純稚的數據類)
現象:Data Class指:一些Class擁有Fields,以及用來訪問Fieldsgetter/setter函數,但是沒有其他的功能函數。(感覺這些Data Class如同Entity ClassParameter Class,用來傳遞參數,我認爲這種情況下沒有必要重構。)
重構策略:找出其他class中訪問Data Class中的getter/setter的函數,嘗試以Move Method將這些函數移植到Data Class中,實現將數據和操作行爲(方法)包裝在一起,也讓Data Class承擔一定的責任(方法)。
 
21Refused Bequest(被拒絕的遺贈)
現象:Subclass不想或不需要繼承superclass的部分函數和Field
重構策略:爲subclass新建一個兄弟(sibling class),再運用Push Down MethodPush Down Fieldsuperclass中的相應函數和Field下推到兄弟class,這樣superclass就只包含subclass共享的東西了。其實,也就是將superclass中一些與特定的函數和Field放到特定的subclass中,superclass中僅包含subclass共享的函數和Field
 
如果不想修改superclass,還可以運用Replace Inheritance with Delegation來達到目的。也就是以委託取代繼承,在subclass中新建一個Field來保存superclass對象,去除subclasssuperclass的繼承關係,委託或調用superclass的方法來完成目的。
 
22Comments(過多的註釋)
現象:(暈倒,這個也要重構,Remove掉所有的Comments嗎?不是。)當代碼中出現一段長長的註釋,一般是由於代碼比較糟糕,需要進行重構,除去代碼的壞味道。
重構策略:通過上面提及的各種重構策略,將代碼的壞味道去除,使註釋變成多餘。
如果需要註釋/解釋一段代碼做了什麼,則可以試試Extract Method,提取出一個獨立的函數,讓函數名稱解釋該函數的用途/功能。另外,如果覺得需要註釋來說明系統的某些假設條件,
也可嘗試使用Introduce Assertion(引入斷言),來明確標明這些假設。
 
當你感覺需要撰寫註釋時,請先嚐試重構,試着讓所有的註釋都變得多餘。
 5Divergent Change(發散式變化)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章