《重構》讀書筆記 與 Eclipse 重構功能使用

第二章 重構原則
重構是什麼?
重構(名詞):對軟件內部結構的一種調整,目的是在不改變[軟件之可察行爲]前提下,提高其可理解性,降低其修改成本。
重構(動詞):使用一系列重構準則(手法),在不改變[軟件之可察行爲]前提下,調整其結構。

兩頂帽子:添加新功能和重構,不能同時進行。

爲何重構?
改進軟件設計:可能設計之初根據已有需求,是世界上最優的設計。但是可能過程中增刪許多功能,原有設計已經不滿足現有需求。
使軟件更易理解:
     通常多添加註釋不一定是好的選擇,因爲可能代碼會被別人修改,而忽略修改註釋。後續的人在看註釋反倒成了阻礙理解。
     程序是寫給人看的,不是寫給計算機看的。
找到BUG:重構的過程需要梳理流程,可以找出多個功能,或者多次迭代耦合放到一起的BUG
提高編程速度:便於理解,本身就是提高,新增功能的開發速度。


何時重構?
添加功能時、修改錯誤時、審查代碼時、

重構的難題?
數據庫、修改接口、

何時不改重構?
代碼邏輯混亂,完全不能新增東西,可能某些情況下需要完全重寫,不過還是建議先拆分成多個小模塊,然後逐步重構與重建。
項目已經接近最後期限。

先設計後開發,但是沒必要非得爲求最靈活的設計方案而擔憂。只需要選擇一個最合理的方案,實施然後實踐中不斷嘗試、驗證、反饋、再修改。
因爲永遠不可能在前期預知到後續的變化,而且不可能設計時能考慮到所有細節,也沒有必要。

重構與性能?
三種方式
     時間預算法(評估每次任務執行時間與執行鏈條)
     持續關注法(過程中注意,不過有些時候會導致開發速度變慢,或者程序難以理解)
     開發後期(關注比較大的性能消耗)


重構的起源?
1980年Smalltalk



第三章 代碼的壞味道
3.1 Duplicated Code(重複代碼)
     重複邏輯的函數(同一個類、多個子類中、不同類中)
3.2 Long Method(過長函數)
     一個函數僅有一個功能,在此情況下,很少超過15行
     方法內的功能越多,越難以複用。或者冗餘的複用

3.3 Large Class(過大的類)
3.4 Long Parameter List(過長參數列)
     或者過多的全局變量,可以把變化劃分爲到新類中

3.5 Divergent Change(發散式變化)
     原本一個類中要承載太多功能。

3.6 Shotgun Surgery(霰彈式修改)
     一個功能的開發,需要牽扯到很多地方的修改。爲什麼不把一類功能放入一個類中呢?

3.7 Feature Envy(依戀情結)
     將總是一起變化的東西放到一起。數據和引用這些數據的行爲總是一起變化的。

3.8 Data Clumps(數據泥團)
     一個類中的數據太多,或者太少而分佈在多個類中

3.9 Primitive Obsession(基本類型偏執)
     不願意把一類型的數據源放到一個新建數據類中

3.10 Switch Statements(switch驚悚現身)
     是否使用多態替換

3.11 Paralle lInheritance Hierarchies(平行繼承體系)
     子類都有的函數,抽取到父類中。 或者乾脆新建類以創建對象引用的方式使用。

3.12 Lazy Class(冗贅類)
     本身很複雜,重構後功能是在太少,或者很少地方調用,乾脆把這個類刪除。

3.13 Speculative Generality(誇誇其談未來性)

     過度設計,說是爲了以後便於擴展,但是可能很久是隻是單一的使用。


3.16 Middle Man(中間人)
     某個類一半的函數都是委託給其他class。

3.14 Temporary Field(令人迷惑的暫時字段)
     零散的數據,抽到一個新建數據類中


3.15 Message Chains(過度耦合的消息鏈)
     A對象一個功能需要調用B -> C -> D -> F -> E。最後E中才有想要的功能,是否可以把過程都直接抽取到一起,A直接調用抽取後的對象。(公共對象)

3.17 Inappropriate Intimacy(狎暱關係)
     父類與子類之間過度調用,或者兩個類見,過多的互相調用,乾脆把過度調用的方法放到一起。

3.18 Alternative Classeswith Different Interfaces(異曲同工的類)
     類或者函數差不多


3.19 Incomplete Library Class(不完美的庫類)
     爲類庫添加擴展

3.20 Data Class(純稚的數據類)
3.21 Refused Bequest(被拒絕的遺贈)
     某些類的子類,並不需要其父類的功能


3.22 Comments(過多的註釋)




第六章 重新組織函數
6.1 Extract Method(提煉函數)
動機:
     函數粒度更小,複用的機會更大。
     高層函數讀起來像一系列註釋
     函數的複寫也會容易些
做法:
Eclipse 選中幾行代碼,選擇Refactory -> Extract Method 或者使用快捷鍵(Alt + Shift + M)
任意選中一塊代碼,自動轉換爲函數,自動添加參數返回類型。

6.2 Inline Method(內聯函數)
動機:有時可能發現直接使用函數中的代碼比抽取爲函數更便於理解。
做法:
Eclipse 選中函數名,選擇Refactory -> Inline或者使用快捷鍵Alt + Shift + I
把調用此函數的地方直接替換成此函數的內容。選中任意函數纔可使用此功能。(有All invocations 與 Only the selected invocation兩個選項)

6.3 InlineTemp(內聯臨時變量)
動機:某些臨時變量可能印象重構
直接修改爲對變量的使用,而刪除臨時變量

6.4 ReplaceTempwithQuery(以查詢取代臨時變量)
動機:阻礙抽取函數,把計算放入固定函數中

6.5 IntroduceExplainingVariable(引入解釋性變量)
Eclipse 選中表達式,選擇Refactory -> Extract Local Variable或者使用快捷鍵Alt + Shift + L
通常用於表達式,把其中一個抽取爲本地的變量,例如3 + 5 抽取爲 int i = 3;

6.6 SplitTemporaryVariable(分解臨時變量)
動機:一個臨時變量僅被賦值一次,如果賦值多次說明承擔了一個以上的職責。這樣容易讓閱讀者糊塗。

6.7 RemoveAssignmentstoParameters(移除對參數的賦值)
值傳遞(pass-by-value)
     http://www.blogjava.net/heis/archive/2009/04/23/267256.html
引用傳遞(pass-by-refrence)


6.8 ReplaceMethodwithMethodObject(以函數對象取代函數)
     如果發現一個函數中有大量的臨時變量導致無法拆分成細粒度的函數,可以把此函數放入新類中,這樣函數中的臨時變量就成了此類中的全局變量,可以隨意抽取細粒度函數。


6.9 SubstituteAlgorithm(替換算法)






第七章 在對象之間搬移特性


在對象設計過程中,決定把職責放在哪兒是非常重要的一件事情。通常很苦惱,但是運用重構可以改變自己原本的設計。

7.1 Move Method(搬移函數)
動機:兩個類之間有太多耦合行爲,這樣可以通過搬移函數,使得兩個類變得乾淨利落。
做法:
     把要搬移的函數設置爲static,這樣可以看到所有此方法中對當前類全局變量、方法的引用,把這些替換成函數的參數傳入。
     然後使用Eclipse 選中函數名,選擇Refactory -> Move 或者使用快捷鍵(Alt + Shift + V)把字段移到其他類、把類移到其他包

7.2 Move Field(搬移字段)
動機:隨着系統的發展,發現自己需要新的classes,並需要將原本的工作責任拖到新class中。
做法:使用Eclipse 選中字段,選擇Refactory -> Move 或者使用快捷鍵(Alt + Shift + V)把字段移到其他類、把類移到其他包

7.3 Extract Class(提煉類)
動機:很多數據散落在各處,可以把邏輯上可以劃分的數據放到一類中。
做法:使用Eclipse 選中字段,選擇Refactory -> Extract Class
把所有選中字段提到新類中,可以選擇新建文件也可以使內部類

7.4 Inline Class(將類內聯化)
動機:類中沒有太多行爲,與Extract class相反。

7.5 Hide Delegate(隱藏“委託關係”)
動機:通過“委託關係”,降低類之間的耦合。

7.6 Remove MiddleMan(移除中間人)
與7.5相反

7.7 Introduce Foreign Method(引入外加函數)
動機:函數的所有參數都是使用同一個數據對象的值,乾脆直接把數據對象傳進入,由函數內部


7.8 Introduce Local Extension(引入本地擴展)
動機:有些無法直接修改源碼的地方需要添加新的支持。
做法:繼承,然後填加功能。




第八章 重新組織數據


8.1 Self Encapsulate Field(自封裝值域)
動機:是直接使用字段還是添加setter/getter總是爭論不休,建議直接使用字段,當必須使用setter/getter時通過此模式自動改變。
做法:
Eclipse 重構工具可以自動完成此類操作。
Eclipse 中選中屬性,選擇Refactor -> Encapsulate Filed。

1. 設置getter/setter名稱
2. 使用此字段處如何修改,use setter and getter(使用set與get方法替換引用、keep field refrence 保持原有字段引用方式不變) -> OK
3. Insert new method after 插入位置

8.2 Replace Data Value with Object(以對象取代數據值)
動機:開發初期可能僅需要簡單的數據項就可以搞定,隨着開發的進行線管數據項越來越多,此時可以把數據值變成對象。
做法:
Eclipse 重構工具可以自動完成此類操作。
Eclipse 在需要抽取字段的類中,選擇Refactor -> Extract Class 。
1. Class name指定新數據名稱
2. Destination 新創建類文件,還是使用內部類形式
3. Select fields for extracted class 可以勾選抽取字段,右側Edit可以編輯類型與字段名
4. Field name 指定當前類中使用此新數據對象的字段名。


8.3 Change Value to Reference (將值改爲引用對象)
動機:
做法:

8.4 Change Reference to Value(將引用對象改爲值對象)

8.5 Replace Array with Object(以對象取代數組)
動機:
做法:
          String[] person = new String[3];
          person[0] = "18";
          person[1] = "name";


修改後效果

8.6 DuplicateObservedData(複製“被監視數據”)
GUI多處顯示的值有關聯或者相同(把這些數據放置到一處)

8.7 ChangeUnidirectionalAssociationtoBidirectional(將單向關聯改爲雙向關聯)
A引用B,希望修改爲A引用B,B也引用A

8.8 ChangeBidirectionalAssociationtoUnidirectional(將雙向關聯改爲單向關聯)
與8.7相反

8.9 ReplaceMagicNumberwithSymbolicConstant(以字面常量取代魔法數)
動機:魔法數字就是直接使用數字,存在的問題是,其他人不知道這個魔法數字是什麼意思,爲什麼必須是這個數字,可能多處使用同樣邏輯的魔法數字,僅修改一處不會可能會忘記其他地方的修改。
做法:
Eclipse 重構工具可以自動完成此類操作。
Eclipse 中 選中需要抽取爲常量的魔法數字,這裏是選中數字30,選擇Refactor -> Extract Constant(提取常量、把任意位置的字符串或者數字抽取爲一個靜態全局常量。所有使用此字符或者數字的也會相應的被替換爲使用常量。)

1. Constant name 設置名稱
2. Access modifier 設置訪問權限
3. Replace all occurrences of the selected expression with references to the constant 如果勾選,會自動替換所以選中數字的地方爲此常量
4. Qualify constant references with type name 是否使用類型名限定常量引用


8.10 EncapsulateField(封裝字段)
沒看出與 “8.1 Self Encapsulate Field(自封裝值域)” 有啥區別

8.11 EncapsulateCollection(封裝集合)
集合(列表、數組)函數不建議提供setter/getter, 前者會直接覆蓋掉整個集合,而且不方便反向查找。後者直接獲得了所有集合的控制權。
建議修改爲add/remove函數來間接操作集合。

8.12 ReplaceRecordwithDataClass(以數據類取代記錄)
把邏輯上一種類型的所有數據,封裝到一個新類中。

8.13 Replace Type Code with Class(以類取代類型碼)

8.14 ReplaceTypeCodewithSubclasses(以子類取代類型碼)

8.15 以State/Strategy取代識別碼
動機:以State Object (專門用來描述狀態的對象)取代type code
做法
1. 使用Self Encapsulate Filed 將type code自我封裝起來。
2. 新建一個class,根據type code用途命名。這就是State Object
3. 爲新創建的class添加subclasses,每個subclasses對應一個type code
4. 在supperclass中建議一個抽象的查詢函數,用於返回type code。每個subclass中覆寫此函數,返回確切type code。
5. 編譯
6. 在source class中建立一個值域,用以保存新建state object。
7. 調整source class中負責查詢type code的函數,將查詢動作轉發給state object
8. 編譯,測試。


State 模式

8.16 Replace Subclass with Fields(以值域取代子類)
動機:如果每個子類中都返回常量,使用接口與多個子類已經多大意義。
做法:





第九章 簡化條件表達式

9.1 Decompose Conditional(分解條件表達式)
動機:判斷過多通常導致可讀性下降
做法:把if判斷中的條件抽取爲方法

9.2 Consolidate Conditional Expression(合併條件表達式)
動機:有時發現一串條件檢查,檢查條件各不相同,但是最終行爲卻一致。
做法:合併多個判斷,返回相同的值。

9.3 Consolidate Duplicate Conditional Fragments(合併重複的條件片段)
動機:如果if與else 中都執行的代碼,爲何不乾脆放到if與else的外面?

9.4 Remove Control Flag(移除控制標記)
動機:Java依然支持條件語句中的標記,雖然不太常用的,但是如果使用會導致程序很難理解。

9.5 Replace Nested Conditional with Guard Clauses(以衛語句取代嵌套條件表達式)
動機:如果兩條分支都是正常行爲,就應該使用if else 。但是如果某些條件極其罕見,就應該單獨檢查改條件,然後直接返回。這種通常稱爲(衛語句guard clauses)
可以通過直接判斷返回的形式,檢查if嵌套的層級。

9.6 Replace Conditiona lwith Polymorphism(以多態取代條件表達式)??
把swtich修改爲多態形式
動機:多態最根本的好處是:如果你需要根據對象的不同類別而採取不同的行爲,多態使你不必編寫明顯的條件式。

9.7 Introduce Null Object(引入Null對象)
動機:很多地方判斷某個對象是否爲空,爲空如何處理,不爲空如何處理。乾脆判斷一次,如果爲空返回一個null object,由其內部爲所有字段賦予初始值,這樣不會導致異常,也不需要太多if(value == null)的判斷。

9.8 Introduce Assertion(引入斷言)
動機:檢查一定必須爲真的條件。不要濫用,可以在編譯的時候通過腳本刪除所有斷言。









第十章 簡化函數調用

10.1 Rename Method(函數改名)
動機:爲一個方法起一個好的名字便於程序的理解。可以通過Eclipse outline視圖查看方法,像讀文章一樣。
做法:使用Eclipse 選中方法名,選擇Refactory -> Rename或者使用快捷鍵Alt + Shift + R
可以對任意變量、類、方法、包名、文件夾進行重新命名,並且所有使用到的地方會統一進行修改。

10.2 Add Parameter(添加參數)
10.3 Remove Parameter(移除參數)
動機:隨着系統的開發,添加與移除參數,或者修改參數的位置都是常見的重構。原有參數不能滿足現有需求,或者一些參數並未使用到
做法:使用Eclipse 選中方法名,選擇Refactory -> Change Method SignatureAlt + Shift + C或者使用快捷鍵
對方法進行操作,可以修改方法名、訪問權限、增加刪除方法參數、修改參數順序、添加方法異常

10.4 Separate Query from Modifier(將查詢函數和修改函數分離)
動機:將查詢結果緩存下來,後續重複查詢就可以大大加快速度。

10.5 Parameterize Method(令函數攜帶參數)
動機:多個函數執行的流暢一樣,但是每個可能因爲僅修改某個值,可以把這些值抽取到一個參數上,然後多處複用此方法傳入不同參數。

10.6 Replace Parameter with Explicit Methods(以明確函數取代參數)
動機:如果函數中需要通過參數判斷做哪些行爲,乾脆爲這個參數再創建一個新的函數進行處理。

10.7 Preserve Whole Object(保持對象完整)
動機:函數參數用到同一個對象的多個參數,如果增加或者刪除參數的話,需要對所有使用到的地方進行修改。
做法:把所有參數替換爲此對象,函數內部從此對象上取值。

10.8 Replace Parameter with Methods(以函數取代參數)
動機:如果函數的參數可通過其他函數獲取到,沒必要把它作爲函數的參數傳入,直接在函數內部調用其想要當參數傳入的函數即可。
過長的參數列表,不利於維護與理解。


10.9 Introduce ParameterObject(引入參數對象)
動機:把多個參數,如果是可以抽取爲一種類型的,把這些字段都放入新建數據類中,然後進傳遞此數據類即可。


10.10 Remove Setting Method(移除設值函數)
動機:一些參數並不需要被改變,可以刪除其setter函數達到效果。


10.11 Hide Method(隱藏函數)
動機:把某些數據隱藏起來,某些行爲其他地方不能觸發。可以把函數的權限調低。public -> private


10.12 Replace Constructor with Factory Method(以工廠函數取代構造函數)
動機:取消以type code創建對象


10.13 Encapsulate Downcast(封裝向下轉型)
動機:返回Object然後在向下轉型,如果可以直接返回固定的類型。


10.14 Replace Error Code with Exception(以異常取代錯誤碼)
動機:一些異常直接返回數值,爲何不乾脆throw new exception?


10.15 Replace Exception with Test(以測試取代異常)
動機:異常不能成爲條件檢查的替代品






第十一章 處理概括關係


11.1 PullUpField(字段上移)
11.2 PullUpMethod(函數上移)
11.3 PullUpConstructorBody(構造函數本體上移)
11.4 PushDownMethod(函數下移)
11.5 PushDownField(字段下移)
11.6 ExtractSubclass(提煉子類)


第十二章 大型重構


12.1 Tease Apart Inheritance(梳理並分解繼承體系) 
建立兩個繼承體系,並通過委託關係讓其中一個可以調用另外一個。

12.2 Convert Procedural Design to Objects(將過程化設計轉化爲對象設計) 
典型情況:class中有着長長的過程函數和極少的數據,以及所謂的亞數據對象,處了數據訪問函數外沒有其他任何函數。

12.3 Separate Domain from Presentation(將領域和表述/顯示分離) 
將domain logic(領域邏輯)分離出來,爲它們建立獨立的domain classes
動機:MVC模式,將用戶界面代碼(view) 和 領域邏輯(model 模型)分離了。

12.4 Extract Hierarchy(提煉繼承體系) 
動機:設計之初僅需要一個類完成功能 ,實現過程中發現一個類中有2,3個功能。可以把這些新增的功能作爲子類實現。




第十三章 重構,複用與現實 


13.1 現實的檢驗 
只有已經開發完成一版,再次基礎上新增功能,纔會強烈的對重構的需求。


13.2 爲什麼開發者不願意重構他們的程序 
1. 不知道如何重構
2. 短視,工期壓力比較大
3. 代碼重構是新工作量
4. 重構可能引入BUG


13.3 再論現實的檢驗 


13.4 重構的資源和參考資料 


13.5 從重構聯想到軟件複用和技術傳播 


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章