重構改善既有代碼的設計

本系列是用來記錄《重構,改善既有代碼的設計》這本書的讀書筆記。方便本身查看,也方便你們查閱。java

欲速則不達,欲達則欲速!算法

重構,絕對是寫程序過程當中最重要的事之一。在寫程序以前咱們不可能事先了解全部的需求,設計確定會有考慮不周的地方,並且隨着項目需求的修改,也有可能原來的設計已經被改的面目全非了。更況且,咱們不多有機會從到到尾完成一個項目,基本上都是接手別人的代碼,即便這個項目從頭至尾參與,也有可能接手其它組員的代碼。咱們都有這樣的經驗,看到別人的代碼時感受就像屎同樣,有一種強烈的想重寫的衝動,但必定要壓制住這種衝動,徹底重寫,可能比原來好一點,但浪費時間不說,還有可能引入原來不存在的bug,並且,你不必定比原來設計的好,也許原來的設計考慮到了一些你沒考慮到的狀況。咱們寫的代碼,終有一天也會被別人接手,頗有可能到時別人會有和咱們如今同樣的衝動。因此,咱們要作的重構,從小範圍的重構開始。編程

重構不僅是能夠改善既有的設計,還能夠幫助咱們理解原來很難理解的流程。好比一個複雜的條件表達式,咱們可能須要好久才能明白這個表達式的做用,這時候,抽象出來,起一個易於理解的名字,函數名字很重要,下次再見到的時候,天然知道當初的想法了,好的代碼賽過註釋,畢竟註釋有可能更新的不是很及時。c#

《重構,改善既有代碼的設計》,這是一本經典之做,看過這本書要收穫的是,讓重構融入整個寫代碼的過程當中,讓重構再也不做爲一項獨立的任務,而是在寫代碼的過程當中隨時隨地的進行,一個函數不容易理解,重構;添加新功能時很不方便,重構。數組

定義

對軟件內部結構的一種調整,使用一系列重構手法,在不改變軟件可觀察行爲的前提下,調整其結構,提升其可理解性,下降其修改爲本。函數

爲什麼重構
  • 重構改進軟件設計:原來的設計不可能考慮到全部的狀況,隨意添加功能修改東西,可能已經看不出本來的設計了
  • 重構使軟件更容易理解
  • 重構幫助找到bug:重構能夠增長對代碼的理解,從而容易發現bug
  • 重構提升編程速度:重構雖然花費時間,可是重構能夠改善程序的設計,使程序更不容易出現bug,使添加新特性更容易
什麼時候重構 無須專門撥出時間進行重構,重構應該隨時隨地進行,事不過三,三則重構,當添加新功能時若是不是特別容易,能夠經過重構使添加新特性更容易,修補錯誤時重構能夠更容易發現bug,複審代碼也是重構的好時機
代碼的壞味道
  • 重複代碼
  • 過長函數
  • 過大的類
  • 過長參數列
  • 發散式變化:一個類受多種變化的影響
  • 散彈式修改:一種變化引起多個類響應修改
  • 依戀情結:函數對某個類的興趣高過對本身所處類的興趣,是時候考慮這個函數到底應該放在什麼位置了
  • 數據泥團:兩個類中相同的字段,許多函數中相同的參數,這時候就可讓他們擁有本身的類了,簡而言之,相似的東西寫一個類裏
  • 基本類型偏執:編寫小對象,如表示範圍的range
  • switch驚悚現身:switch帶來重複,一樣的switch語句常常散佈於不一樣的地址,若是要加一個新的case子句,就必須找到全部switch語句並修改他們
  • 平行繼承體系:每當你爲某個類增長一個子類,必須也爲另外一個類增長一個子類,大多數時候你會發現,某個繼承體系的類名前綴和另外一個繼承體系的類名前綴徹底相同,是時候分離爲兩個繼承體系了
  • 冗贅類:沒啥用的類就應該幹掉
  • 誇誇其談的將來性:無用的抽象類,無用的預留參數
  • 使人迷惑的局部變量
  • 過分耦合的消息鏈:函數過大時,就應該提取函數
  • 中間人:無用的委託,過多中間層
  • 殊途同歸的類:不一樣的類或函數,幹相同的事,寫一個很差嗎
  • 過多的註釋:若是一個函數須要過多的註釋,是時候重構了,把代碼當註釋,好的名字就是註釋
重構組織函數
  • 提煉函數:代碼粒度越小越容易重用,並且這部分代碼被組織到一塊兒後,能夠起一個易於理解的名字解釋其意圖。要注意臨時變量的處理
  • 內聯函數:對於一些函數,可能其實現和名字同樣容易理解,不必再封裝一層;或者一些不必的中間層均可以去掉,java中用final修飾,C++中用inline修飾,c#中沒有內聯函數,也許微軟認爲C++才須要關心性能,而C#關注快速開發,沒必要理會這些開銷吧。
  • 內聯臨時變量:你有一個臨時變量,只被一個簡單表達式賦值一次,而它妨礙了其它重構手法
  • 以查詢取代臨時變量:應減小定義臨時變量,如程序以一個臨時變量保存某一表達式的運算結果並做爲方法的返回值,直接用表達式做爲返回結果就算了
  • 引入解釋性變量:有時候也會遇到很是複雜的表達式,定義一個臨時變量,解釋其用途也是能夠的
  • 分解臨時變量
  • 移除對參數的賦值:不要對函數傳進來的參數賦值
  • 創建新的類取代函數:有一個大型函數,這個函數有太多的臨時變量,以致於不能重構,這時就能夠另起爐竈,新建一個類,原來函數的參數就變成了新的類成員,能夠方便的對新的勒種響應的函數進行各類重構。
  • 替換算法:將兩個方法中相同的部分,提取出來
在對象之間搬移特性
  • 搬移函數:類中的一個函數使用另外一個類的對象的次數比使用本身所在類的對象的次數還要多,頗有可能這個函數定義錯地方了
  • 搬移字段
  • 提煉類
  • 內聯類
  • 隱藏委託關係:有時調用一個對象的方法時得到另外一個對象,而後再調用得到對象的一個方法得到另外一對象,如此反覆,此時能夠直接調用第一個對象再加一個方法直接返回最終對象
  • 移除中間人
從新組織數據
  • 自封裝數據:爲字段創建賦值取值函數
  • 以對象取代數據值:有一個數據項,可能須要對這個數據項添加一個行爲,把這個數據項封裝爲一個類
  • 將值對象改成引用對象
  • 將引用對象改成值對象:對象很差控制時改成不可變的值對象,這樣就不用了考慮同步的問題了。
  • 以對象取代數組:一個數組中的不一樣元素表示不一樣的東西,不容易理解
  • 複製「被監視數據」:主要針對GUI中的數據,實現UI與邏輯分享,MVC,將V中的數據複製到M中,並在M中數據變化時經過listener進制或observer弄死同步更新UI
  • 封裝字段:public改成private,提供相應的訪問函數
簡化條件表達式
  • 分解條件表達式:有一個複雜的條件表達式,將if條件,then、else中的三個段落所有提取出獨立函數以方便理解
  • 合併條件表達式
  • 合併重複的條件判斷:在條件表達式的每一個分支上有着相同的一段代碼,把這段代碼搬移到條件表達式以外
  • 移除控制標記:不用經過控制標記來決定是否退出循環或跳過函數剩下的操做,直接break或return
  • 以衛語句取代嵌套表達式:若是某個條件不常見,應該單獨檢查該條件,這種操做成爲衛語句
  • 以多態取代條件表達式
  • 引入null對象
  • 引入斷言
簡化函數調用
  • 函數更名:好的函數名字很重要,名字起得好能夠看出函數具體是作什麼的,對於理解複雜邏輯很是有幫助
  • 添加參數
  • 移除參數
  • 將查詢函數和修改函數分離
  • 令函數攜帶參數
  • 以明確函數取代參數:有一個函數,其中徹底取決於參數值而採起不一樣的行爲,針對該參數的每個可能性,創建一個單獨的函數
  • 引入參數對象:某些參數老是同時出現,先建一個變量取代這些參數,減小參數的數量
  • 移除賦值函數:若是類中的某個應該在對象建立時被賦值,此後再也不改變,不要添加賦值函數
  • 隱藏函數:有一個函數歷來沒有被其它類用到,或者原本被用到,但隨着類添加接口,以後就用不到了,那麼隱藏這個函數,也就是減少做用域
  • 封裝向下轉型:若是返回的值必定須要調用者轉型,那麼最好在函數中完成轉型動做
  • 以異常取代錯誤碼
  • 以測試取代異常:異常只應該被用於異常的、罕見的、意料以外的行爲,不該該做爲條件檢查用
處理歸納關係
  • 字段上移:連個子類擁有相同的字段,將該字段移至父類消除重複
  • 函數上移:有些函數在各個子類中產生相同的結果,上移至父類消除重複並方便修改
  • 構造函數本體上移
  • 函數下移
  • 字段下移
  • 提煉子類
  • 提煉父類:兩個類有類似的特性,爲這兩個類創建一個父類,將相同特性移至父類
  • 體鏈接口
  • 摺疊繼承體系:父類與子類無太大區別,以前沒考慮明白,消除繼承關係,合併在一塊兒
  • 塑造模板函數
  • 以委託取代繼承:某個子類只使用父類接口中的一部分,將父類做爲子類的一個字段,消除繼承關係
  • 以繼承取代委託:一個類的行爲基本上都是委託另外一個類,當另外一個類接口改變時也要同時修改委託類,直接繼承省事方便
大型重構
  • 梳理並分解繼承體系:某個繼承體系同時承擔兩項責任,創建兩個繼承體系,並經過委託關係讓其中一個調用另外一個
  • 將過程化設計轉化爲對象設計
  • 將領域和表述/顯示分離
  • 提煉繼承關係:某個類作了太多的事情,其中一部分工做是以大量條件表達式完成的,創建繼承體系,以一個子類表示一種特殊狀況
相關文章
相關標籤/搜索