Java, Only Java!

          統計

          留言簿(20)

          積分與排名

          好友空間

          文檔技巧

          閱讀排行榜

          評論排行榜

          《重構》的讀書筆記

        1. 總覽
        2. 第1章 重構,第一個案例
        3. 第2章 重構原則
        4. 第3章 代碼的壞味道
        5. 第4章 構筑測試體系
        6. 第12章 大型重構359
        7. 第13章 重構,復用與現實379
        8. 第14章 重構工具401

            《重構》的讀書筆記

            學習基礎:

            熟悉《設計模式》的基本概念,熟悉基本的Java語法,熟悉Eclipse和JUnit的使用,有相對較好的英語基礎。

            學習過程:

            • 先看第1章,手工輸入實例程序,了解重構的方法和過程。重點是理解重構的思路,最好的理解方式就是通過實踐的方式理解。
            • 再看第2~4章,內容為選擇性閱讀,沒興趣或者看不懂的都可以跳過,因為后面還可以回頭再讀。
            • 接著第5~12章,最好按順序把代碼一個個輸入,再按照作者的步驟重構操作一次,并結合自己以往工作中的實踐來理解。

            學習目的:

            使自己編寫的代碼更容易被人讀懂。

            學習感悟:

            • 代碼的重構應該是一步步完成的,每次重構的部分不要超過自己的理解能力的5%。雖然這樣操作略顯繁瑣,但是可以減輕頭腦重構過程中的記憶強度,減少代碼出錯的機會。
            • 代碼的重構一定要配合JUnit(TDD,測試驅動開發)完成,再加上Git(版本管理)和Eclipse(IDE的重構工具)那就事半功倍了。

            學習代碼:

            Refactored-MartinFowler

            總覽

            • 第1章(必讀),從實例程序出發,了解重構的方法和過程。
            • 第2章,討論重構的一般性原則、定義和進行重構的原因。
            • 第3章,介紹如何判斷問題代碼,以及如何用重構改善它們。
            • 第4章,在代碼中構建java的測試環境
            • 第5~12章,具體面對的問題和重構的方法。
            • 第13章,Bill Opdyke在商業開發中應用重構
            • 第14章,自動化重構工具(今天看來,已經不是太大問題,Eclipse的Refactor已經非常好用)
            • 第15章,重構的哲學思想

            第1章 重構,第一個案例

            1.1 (P1)起點

            因為代碼的結構無法滿足添加新功能的需要,因此先進行重構,使代碼方便添加新功能,然后再添加新功能。

            1.2 (P7)重構的第一步

            首先確認自己擁有一套可靠的測試機制,因為重構有可能引入問題,通過測試保證重構沒有改變程序功能。

            第2章 重構原則

            2.1 (P53)何謂重構

            重構(名詞):對軟件內部結構的一種調整,目的是在不改變軟件可觀察行為的前提下,提高理解性和降低修改成本。
            重構(動詞):使用一系列重構手法,在不改變軟件可觀察行為的前提下,調整其結構。
            定義的擴展:

            • 重構讓軟件更容易理解和修改
            • 重構不會改變軟件的可觀察行為,即使改變也只能是微小的影響,軟件功能一如既往。

            重構的目標:只改變程序內部結構,不添加新的功能

            不要一心二用:

            • 添加新功能的時候,不重構已有代碼。
            • 重構代碼時,不增加新功能。

            2.2 (P55)為何重構

            • 重構改進軟件設計:
              • 程序的設計質量在沒有持續重構的情況下逐漸變差,功能的增加或者修改都可能使代碼越來越難以理解和維護,就越難保證最初的設計目標
              • 消除重復的代碼一方面是程序運行更快,一方面是方便未來的修改,例如:重構減少代碼重復,避免功能的改變需要修改多處代碼。
            • 重構使軟件更容易理解:
              • 及時填補“想要它做什么”和“告訴它做什么”之間的縫隙。重構的核心就是要“準確說出我所要的”
              • 重新閱讀代碼的人有可能是自己,也可能是他人。
              • 通過重構可以把自己不熟悉的代碼的用途梳理一遍,加深對代碼的理解
            • 重構幫助找出bug:
              • 這個是建立在代碼容易理解之上的
            • 重構提高編程速度:
              • 重構達到良好的設計,而良好的設計更容易修改代碼、增加功能、問題調試。

            2.3 (P57)何時重構

            重構的三次法則:

            • 第一次開發某個功能的時候以實現為目標。
            • 第二次開發相同功能的時候,克制自己的反感,繼續重復實現。
            • 第三次開發相同功能的時候,應該重構。

            重構的時間點:

            • 添加功能時重構:
              • 一方面可以幫助理解需要修改的代碼
              • 一方面是使現在以及未來增加新功能更加容易。
            • 修補錯誤時重構:
              • 出現bug的時候,難以找出問題所在的時候,很有可能是代碼不清晰導致查找bug的困難。
            • 復審代碼時重構:
              • 復審代碼有助于知識的傳播,有利于代碼被編寫者之外的人理解。
              • 重構加深了對代碼的理解,有利于提升復審代碼的能力

            復審團隊:只要代碼作者和一個審查者者。較大的項目可以通過UML圖去展示代碼的邏輯。

            程序難以修改的原因:

            • 難以閱讀的程序
            • 邏輯重復的程序
            • 添加新特性需要修改已有代碼的程序
            • 帶復雜邏輯判斷的程序

            重構的目標:

            • 代碼容易閱讀
            • 所有邏輯都只有唯一地點指定
            • 新的改動不會危及現有行為
            • 盡可能簡單表達邏輯

            2.4 (P60)怎么對經理說

            • 懂技術的經理,很容易溝通;
            • 追求質量的經理,介紹重構對質量的幫助;
            • 追求進度的經理,則自己安靜地重構。因為重構可以最快的完成任務,就是對經理最大的幫助。

            間接訪問

            很多時候重構都為程序引入間接訪問:

            • 把大型對象拆分成小對象
            • 把大型函數拆分為小型函數。

            間接訪問的價值:

            • 允許邏輯共享:一個函數在不同地點被調用。子類共享超類的方法。
            • 分開解釋意圖和實現:通過類名和函數名解釋自己的意圖
            • 隔離變化:在不同地方使用同一個對象,需要修改一處邏輯,那么可以做出子類,并在需要的時候修改這個子類。
            • 封裝條件邏輯:運用多態。將條件邏輯轉化為消息模式。

            減少間接層的條件:當間接層只在一處使用,那么需要將其消除。

            2.5 (P62)重構的難題

            數據庫重構:

            • 存在問題:
              • 程序與數據庫耦合在一起。
              • 數據遷移。
            • 解決方案:
              • 在非關系型數據庫,可以在數據庫和對象模型中插入一個分離層,隔離兩者之間的變化

            接口重構

            • 對于已經發布的接口需要可能需要維護舊接口和新接口,用deprecated修飾舊接口。
            • 不發布新接口,在舊接口中調用新接口。
            • 假如新接口拋出編譯時異常,那么可以在舊接口中調用新接口并將編譯時異常轉化為運行時異常。

            不重構的條件:

            • 重構之前,代碼在大部分情況下都能夠正常運行,就可以重構,否則應該是重寫。
            • 到了Deadline,應該避免重構。

            2.6 (P66)重構與設計

            重構與設計是彼此互補的:

            • 設計應該在編碼之前,但是設計總有缺陷,隨著對問題認識的逐漸深入,通過重構可以改善設計的質量。
            • 重構減輕了設計的難度和壓力,在程序不斷修改的過程中逐步完善程序的設計。

            2.7 (P69)重構與性能

            重構是有可能導致程序運行變慢的,但是不需要在設計和編碼時就考慮性能問題。例如:實時程序的編寫:

            • 首先寫出可調的程序
            • 然后調整它以達到性能的要求。
              • 經過分析大部分程序的主要時間是消耗在小部分代碼上,所以不用對所有代碼進行優化。
              • 性能優化放在開發的后期,利用分析工具找出消耗大量時間的代碼,然后集中優化。

            第3章 代碼的壞味道

            3.1 (P76)Duplicated Code(重復代碼)

            • 同個類兩個函數存在相同表達式:Extract Method(提煉函數)
            • 互為兄弟類內存在相同表達式:
              • Extract Method→PullUp Method(函數上移)
              • 如果代碼只是相似:先運用Extract Method(提煉函數)分開再Form TemPlate Method(塑造模板函數)
            • 兩個毫不相干的類存在重復代碼:Extract Class(提煉類)

            3.2 (P76)Long Method(過長函數)

            原則:盡量利用函數名稱來解釋用途,而不是注釋。
            關鍵:代碼主要用來描述“做什么”,而不是描述“怎么做”。例如:getAge()表達獲取年齡,而today-birthday就增加了理解的間接性,雖然看代碼的人也能明白含義,但是就需要多想一下,并且birthday有可能表達的不是某個人的出生日期呢,而是某個買回來的產品的呢?那可能表達的就是使用時長了。
            具體情況:

            • 函數有大量參數和臨時變量:Extract Method(提煉函數)
            • 用Replace Temp with Query(以查詢取代臨時變量)消除臨時變量
            • 用Introduce Parameter Object(引入參數對象)或者Preserve Whole Object(保持對象完整)來將多長的參數列表變得簡潔一點。
            • 如果按照上述步驟還存在太多變量和參數就需要用到Replace Method with Method Object(以函數對象取代函數)
            • 條件表達式可以用Decompose Conditional(分解條件表達式)解決
            • 可以將循環內的代碼提煉為函數。

            3.3 (P78)Large Class(過大的類)

            有時候類并非在所有時刻都使用實例變量:使用Extract Method和Extract Subclass(提煉子類)

            類中有太多代碼:

            • Extract Class(提煉類)
            • Extract Subclass(提煉子類)
            • Extract Interface(提供接口)分解類的行為。存在GUI的時候,可以Duplicate Observed Data(復制“被監視數據”),分離數據和行為到業務模型中去。

            3.4 (P78)Long Parameter List(過長參數列)

            • 如果可以調用已有對象獲取的話可以使用Replace Parameter with Methods(以函數取代參數)
            • 將來自同一對象的數據收集起來,以該對象替代:Preserve Whole Object(保持對象完整)
            • 如果幾個參數總是同時出現,那么可以考慮Introduce Parameter Object(引入參數對象)

            3.5 (P79)Divergent Change(發散式變化)

            不同的變化影響著相同的類發生改變,即變化的認知有分歧(Divergent)。通過Extract Class把不同的功能封裝到不同的類中,使每個類只因一種變化而需要修改

            3.6 (P80)Shotgun Surgery(霰彈式修改)

            相同的變化會涉及到多個類發生修改,類似霰彈槍射擊的效果。
            可以通過Extract Method,Move Method,Inline Class把一種變化產生的多個修改移到同一個類中。

            對比:

            • Divergent Change(發散式變化)是一個類受到的多個變化影響;
            • Shotgun Surgery(霰彈式修改)是一個變化引起多個類需要修改。

            3.7 (P80)Feature Envy(依戀情結)

            類中的某個函數對其他類的依賴度過高,則應該通過Move Method(移動函數)將它搬移到合適的類中。

            3.8 (P81)Data Clumps(數據泥團)

            數據項總是成群結隊出現,通過Extract Class將它們提煉到一個獨立對象中,從而縮短參數列表,簡化函數調用。

            判斷數據項是否相關的方法:如果這些數據項不在一起時就失去了明確的含義,那么就可以把它們提煉成一個新的對象。

            3.9 (P81)Primitive Obsession(基本類型偏執)

            • 有些字段可以用對象表示更準確Replace Data Value with Object(以對象取代數據值)
            • 對于不影響行為的類型碼可以Replace Type Code with Class(以類取代類型碼)
            • 影響行為的類型碼可以Replace Type Code with Subclasses(以子類取代類型碼),類型碼在運行時會變化就用Replace Type Code with State/Strategy(以State/Strategy取代類型碼)

            3.10 (P82)Switch Statements(switch驚悚現身)

            • 使用Replace Type Code with Subclasses(以子類取代類型碼)或者Replace Type Code with State/Strategy(以State/Strategy取代類型碼)
            • 輕量級的解決方法:Replace Parameter with Explicit Methods(以明確函數取代參數)

            3.11 (P83)Parallel Inheritance Hierarchies(平行繼承體系)

            每當為一個類增加子類必須也為另外一個類增加一個子類,那么就讓一個繼承體系的實例引用另一個繼承體系的實例。

            3.12 (P83)Lazy Class(冗贅類)

            沒用的類,使用Inline Class(內聯類)或者Collapse Hierarchy(折疊繼承體系)來解決

            3.13 (P83)Speculative Generality(夸夸其談未來性)

            • 為未來設計的類,使用Inline Class(內聯類)或者Collapse Hierarchy(折疊繼承體系)來解決
            • 為未來設計的函數參數,使用Remove Parameter(移除參數)
            • 函數名稱啰嗦,使用Rename Method(函數改名)

            3.14 (P84)Temporary Field(令人迷惑的暫時字段)

            對象中某個字段僅為特定情況而設。使用Extract Class(提煉類)將這個字段提取出來

            3.15 (P84)Message Chains(過度耦合的消息鏈)

            消息鏈:用戶通過一個對象獲取另一個對象,再通過獲取的對象請求另一個對象,如此操作就是消息鏈。采取這種方式意味著客戶代碼將與查找過程中的導航結構緊密耦合,可以使用Hide Delegate(隱藏“委托關系”)進行重構。但是謹慎處理!

            3.16 (P85)Middle Man(中間人)

            過度委托形成中間人:Remove Middle Man(移除中間人)

            如果中間人還有其他行為,Replace Delegation with Inherited(以繼承取代委托)

            3.17 (P85)Inappropriate Intimacy(狎昵關系)

            • 兩個類相互依賴過多,花費大量時間去獲取對方的private成員內容,使用Move Field(移動字段)和Move Method(移動方法)減少耦合性,或用Change Bidirectional Association to Unidirectional(將雙向關聯改為單向關聯)
            • 如果兩個類無法移動相同數據和函數,可以使用Extract Class(提煉類),讓他們使用新類進行交互。

            3.18 (P85)Alternative Classes with Different Interfaces(異曲同工的類)

            兩個函數做了相同的事情卻有不同的函數名稱

            3.19 (P86)Incomplete Library Class(不完美的庫類)

            庫函數功能不足,需要增加一些自定義的功能:

            • 需要加入少量操作,使用Introduce Foreign Method(引入外加函數)
            • 需要加入大量操作,使用Introduce Local Extension(引入本地擴展)

            3.20 (P86)Data Class(幼稚的數據類)

            幼稚的數據類:只有數據沒有行為的類,其他類需要對該類的數據進行取值設值操作

            • 使用Encapsulate Field(封裝字段)和Encapsulate Collection(封裝集合)對字段進行合理地封裝
            • 對于不該被其他類修改的字段:Remove Setting Method(移除設值函數)

            3.21 (P87)Refused Bequest(被拒絕的遺贈)

            如果子類不愿意接受超類的所有定義,應該使用Replace inherited with Delegation(以委托取代繼承)來處理子類

            3.22 Comments(過多的注釋)87

            使用Extract Method(提煉方法)來解決注釋過多問題,注釋更多應該說明的是“怎么做”,而不是“做什么”,例如:對一個排序函數說明其采用二分法排序,而不是說明它是個排序函數,因為這個說明在函數名稱中已經具備。

            第4章 構筑測試體系

            4.1 自測試代碼的價值89

            • 確保所有測試都完全自動化,讓它們檢查自己的測試結果;
            • 一套測試就是一個強大的bug探測器,能夠大大縮減查找bug所需要花費的時間。
              • 因為代碼剛剛寫完,測試出現問題后,心里很清楚自己修改或者添加了哪些東西,可能會在哪里出現了問題。

                4.2 JUnit測試框架91

            • 頻繁地運行測試;
            • 每次編譯前都進行一次測試;
            • 每天至少執行一次所有的測試。

              4.3 添加更多測試97

            • 編寫一個測試并運行起來,好過將所有的測試編好了一起運行。
            • 測試特別需要注意可能出錯的邊界條件;
            • 對于可能出錯的地方,還需要檢查是否拋出了預期的異常;
            • 測試不能解決所有bug,但是可以大大減少bug的數量。

            第12章 大型重構359

            大型重構是程序開發必將遇到的,只是不知道在什么時間,用什么樣的方式經歷。例如:隨著時間的推移,河道必定會被水草和垃圾所堵塞,你可以固定時間清淤,也可以放任自流直到崩潰。崩潰后依然會面臨總結經驗教訓,再次重構系統。
            大型重構很難給出具體的操作案例,因為每個大型案例相對于自身來說都是惟一的,是無法復制和重現的??梢詮椭婆c重現的都是這些大型重構中蘊含的具體的細節,因此這章主要講的是思想和理念上的內容。
            四個大型重構:

            • Tease Apart Inheritance(362)用于處理混亂的繼承體系
              • 某個繼承體系同時承擔兩項責任
              • 建立兩個繼承體系,其中一個通過委托調用另一個
            • Convert Procedural Design to Objects(368)如何重構過時的編碼技術遺留下來的程序
              • 傳統過程化風格的代碼
              • 將數據記錄變成對象,將大塊的行為分成小塊,再將它們移入到相關對象中
            • Separate Domain from Presentation(370)將業務邏輯與用戶界面分隔開來
              • 用戶界面類中包含了業務邏輯
              • 將業務邏輯剝離到業務類中,參考:MVC模式
            • Extract Hierarchy(375)將復雜的類轉化為一群簡單的子類,從而簡化系統。
              • 某個類做了太多工作
              • 某個類的部分工作是由大量的條件表達式完成的
              • 建立繼承體系,使用子類表示每一種特殊情況

            第13章 重構,復用與現實379

            作為一個博士寫的內容,仍然具有學術性較強的風格,可以當作歷史資料了解一下重構的發展過程,也可以對重構的思想有更多理論上的認識。

            安全重構(391)

            安全重構的四條籬笆:

            • 相信你自己的編碼能力;
            • 相信你的編譯器能捕捉你遺漏的錯誤;
            • 相信你的測試套件能捕捉你和編譯器都遺漏的錯誤;
            • 相信代碼復審能捕捉你、編譯器和測試套件都遺漏的錯誤。注:沒有100%安全的重構,但是可以通過以上的條件滿足你對安全性的最低要求。

            重構工具

            • Eclipse(或其他IDE)自帶的重構工具:Refactor;
            • Java(或其他編譯器)自帶的分析工具:lint;
            • JUnit等自動化的測試工具。

            第14章 重構工具401

            相對于10多年前寫的內容,現在許多IDE都已經提供了對大部分重構功能的支持。但是了解重構的基本理念,對于正確地使用重構工具會有很大的幫助。因為成功的重構不依賴于工具,而決定于人,當人做出了正確的決定,合理地使用重構工具輔助自己,才能保證重構的完成。

          • posted on 2019-01-16 17:48 zYx.Tom 閱讀(210) 評論(0)  編輯  收藏 所屬分類: 6.我的感想

            主站蜘蛛池模板: 阳高县| 本溪| 略阳县| 巴青县| 临猗县| 邢台县| 盐亭县| 宜丰县| 紫云| 无棣县| 清河县| 松阳县| 巴里| 林周县| 光泽县| 青川县| 贵州省| 和政县| 磐石市| 九龙坡区| 屏边| 横山县| 武宣县| 隆安县| 同仁县| 平塘县| 荥阳市| 铜山县| 滦平县| 高阳县| 久治县| 通山县| 长岭县| 江安县| 衢州市| 合川市| 丹凤县| 五大连池市| 闻喜县| 仁寿县| 灵寿县|