《重構---改善既有代碼的設計》

哪有什麼天生如此,只是咱們每天堅持。 -Zhiyuan

國慶抽出時間來閱讀這本從師傅那裏借來的書,據說仍是程序員的必讀書籍。
關於書的高清下載鏈接會在文章的最後呈現。
話很少說讓咱們一塊兒去領略一下書中風采。程序員

若是你讀到這篇文章,說明你也想對你的代碼進行重構,那首先咱們就來看一下什麼是重構?算法

1. 何謂重構?

兩個定義:數據庫

  1. 名詞形式
    重構(名詞)對軟件內部的一種太縱橫,目的是在不改變軟件可觀察的前提下,提升其可理解性,下降其修改爲本。
  2. 動詞形式
    重構(動詞)使用一系列的重構手法,在不改變軟件可觀察的行爲的前提下,調整其結構。

上面是重構的學術解釋,相信一些大神是能夠直接一步到位的領略它的意思。我比較喜歡通俗易懂的解釋:
你要問我「重構就只是整理代碼嗎?」 在某種角度來講 還真是。 but ! 重構確定有其獨到的地方:
它還提供一種 更高效 且 受控的 代碼整理技術。 編程

劃重點哈!數組

做者還對重構進行了另外兩方面的擴展。讓我一塊兒來看下還有什麼好玩的:性能優化

  1. 重構的目的是是軟件更容易被理解和修改session

    也就是說讓你的修改但你只能對軟件的可觀察的外部行爲形成很小的變化甚至不形成變化。
       和重構造成對比的是 **性能優化** (我反正一開始以爲這兩個都是同樣的),
       差就差在性能優化一般不會改變組件的行爲(除了執行速度)指揮改變其內部結構。
       可是!   
       這兩個的出發點不一樣: **性能優化每每是代碼較難理解,可是爲了獲得所需的性能你不得不這麼作。**
  2. 重構不會改變軟件的可觀察的行爲app

    也就是說重構以後軟件的功能一如以往。全部人除了你本身都不知道有東西改變過(就像鬼子進村悄悄的進行,打槍的不要)。

2. 爲什麼重構?

重構有四大好處:框架

  1. 重構改進軟件設計
    若是沒有重構,程序的設計會逐漸腐敗變質。重構很像是在整理代碼,你所作的就是讓全部東西回到應出的位置上。常常性的重構能夠幫助維持本身該有的形態。
  2. 重構使軟件更容易理解
    重構能夠幫助咱們讓代碼更易讀。
  3. 重構幫助找到bug
    對代碼進行重構,能夠幫助咱們深刻理解代碼,對代碼理解的越深,就越能幫咱們找到bug。重構可以幫你們更有效地寫出強健的代碼。(這對程序員來講是重點)
  4. 重構提升編程速度
    重構能夠幫助咱們更快速地開發軟件,由於它阻止系統腐敗變質,它甚至還能夠提升設計質量。(這對Boss來講是重點)
Any fool can write code that a computer can understand. Good programmers write code that humans can understand.

任何一個傻瓜都能寫出計算機能夠理解的代碼。惟有寫出人類容易理解的代碼,纔是優秀的代碼。ide

3. 什麼時候重構?

重構 不是 一件應該特別撥出時間作的事情,重構應該隨時隨地進行。不該該爲重構而重構,之因此重構,是由於咱們想作別的什麼事,而重構能夠幫助咱們把那些事作好。

做者給出了一個三次原則,讓咱們來看一下:

三次法則:事不過三,三則重構。

  1. 添加功能時重構。
  2. 修補錯誤時重構。
  3. 複審代碼時重構。

夠明確了吧! 當你發現你的代碼正符合裏面的條件式那就抓緊時間重構吧。

4. 怎麼對經理說?

此章節只可意會不可言傳。

5. 重構的難題

當你使用一種能夠提升生成力的新技能時,必定要仔細思考此場景是否是適用。 別人的「好媳婦」在你着沒準就沒那麼好用。

假如你發現你確實須要重構,請參考仔細閱讀下面你可能會遇到的難題:

  1. 數據庫難在哪?

    1.絕大多數的程序都和背後的數據庫結構緊密的耦合在一塊兒。
       2.數據遷移
  2. 接口修改難在哪?

    1.已經發布了的接口
  3. 什麼時候不應重構?

    1.代碼根本沒法工做或者太糟糕,重構還不如重寫來的簡單。
    
       2.在項目的最後期限,應該避免重構

中場休息
文章至此,你們應該知道本身如今所處的階段適不適合重構內心有點b數了吧?
不適合就別往下看了,光知道重構是什麼東西就能夠,之後用到再回來看看這篇文章。

下面的文章能告訴你怎麼重構,只是框架,我又不可能把書都抄下來,若是你們真的須要重構不如馬上行動買書看,或者下載個人電子書。(免費的哦~)


6. 代碼的壞味道

  1. 重複代碼(Duplicated Code)
  2. 過長函數(Long Method)
  3. 過大的類(Large Class)
  4. 過長參數列(Long Parameter List)
  5. 發散式變化(Divergent Change):一個類受多種變化的影響
  6. 霰彈式修改(Shotgun Surgery):一種變化引起多個類相應修改
  7. 依戀情結(Feature Envy):函數對某個類的興趣高過本身所處類的興趣
  8. 數據泥團(Data Clumps):相同的若干項數據出如今不一樣地方,這些綁在一塊兒出現的數據應該有屬於它們本身的對象
  9. 基本類型偏執(Private Obsession):不少人不肯意在小任務上運用小對象
  10. switch驚悚現身(Switch Statements):switch語句會在不少地方重複出現,一改則需全改
  11. 平行繼承體系(Parallel Inheritance Hierarchies):當你爲某一個類增長子類時,也必須爲另外一個類相應增長一個類
  12. 冗贅類(Lazy Class):若是一個類不值得存在,那就讓它消失
  13. 誇誇其談的將來星(Speculative Generality):預留的無用的抽象類,無用的抽象參數
  14. 使人迷惑的暫時字段(Temporary Field):類中某個字段只爲某些特殊狀況而設置
  15. 過分耦合的消息鏈(Message Chains):用戶向一個對象請求另外一個對象,而後再向後者請求另外一個對象......
  16. 中間人(Middle Man):無用的委託,過多的中間層
  17. 狎暱關係(Inappropriate Intimacy):兩個類過於親密,一個類過於關注另外一個類的成員
  18. 殊途同歸的類(Alternative Classes with DifferentInterfaces):不一樣名字的類或函數,做者相同的事
  19. 不完美的庫類(Incomplete Library Class):類庫設計不可能完美
  20. 純數據類(Data Class):一個類擁有一些字段以及用於訪問這些字段的函數,除此以外一無長物
  21. 被拒絕的遺贈(Refused Bequest):子類不想繼承超類全部的函數和數據,只想挑幾樣來玩
  22. 過多的註釋(Comments)

7. 構築測試體系

  1. 重構的首要前提是擁有一個可靠的測試環境
  2. 只要寫好一點功能,就當即添加測試,並確保全部測試都徹底自動化,讓它們檢查本身的測試結果。一套測試就是一個強大的bug偵測器,可以大大縮減查找bug所須要的時間。
  3. 撰寫測試代碼的最有用時機是在開始編程以前。當你須要添加特性的時候,先寫相應測試代碼。
  4. 多運用單元測試。測試你最擔憂出錯的地方,考慮可能出錯的邊界條件。不要由於測試沒法捕捉全部bug就不寫測試,由於測試的確能夠捕捉到大多數bug。「花合理時間抓出大多數bug」要好過「窮盡一輩子抓出全部bug」。

8. 從新組織函數

  1. 提煉函數(Extract Method)。你有一段代碼能夠被組織在一塊兒並獨立出來。將這段代碼放進一個獨立函數中,並將函數名稱解釋該函數的用途。
  2. 內聯函數(Inline Method)。一個函數的本體與名稱一樣清楚易懂。在函數調用點插入函數本體,而後移除該函數。
  3. 內聯臨時變量(InlineTemp)。你有一個臨時變量,只被一個簡單表達式賦值一次,而它妨礙了其餘重構手法。將全部對該變量的引用動做,替換爲對它賦值的那個表達式自身。
  4. 以查詢取代臨時變量(Replace Temp withQuery)。你的程序以一個臨時變量保存某一表達式的運算結果。將這個表達式提煉到一個獨立函數中。將這個臨時變量的全部引用點替換爲對新函數的調用。此後,新函數就可被其餘函數使用。
  5. 引入解釋性變量(Introduce ExplainingVariable)。你有一個複雜的表達式。將該複雜表達式(或其中一部分)的結果放進一個臨時變量,以此變量名稱來解釋表達式用途。
  6. 分解臨時變量(Split TemporaryVariable)。你的程序有某個臨時變量被賦值過一次,它既不是循環變量,也不被用於收集計算結果。針對每次賦值,創造一個獨立、對應的臨時變量。

  7. 移除對參數的賦值(Remove Assignments Parameters)。代碼對一個參數進行賦值。以一個臨時變量取代參數的位置。
  8. 以函數對象取代函數(Replace Method with MethodObject)。你有一個大型函數,其中對局部變量的使用使你沒法採用ExtractMethod。將這個函數放進一個單獨對象中,如此一來局部變量就成了對象內的字段。而後你能夠在同一個對象中將這個大型函數分解爲多個小型函數。
  9. 替換算法(Substitute Algorithm)。你想要把某個算法替換爲另外一個更清晰的算法。將函數本體替換爲另外一個算法。

9. 在對象之間搬移特性

  1. 搬移函數(MoveMethod)。你的程序中,有個函數與其所駐以外的另外一個類進行更多交流:調用後者,或被後者調用。在該函數最常引用的類中創建一個有着相似行爲的新函數。將舊函數變成一個單純的委託函數,或是將舊函數徹底移除。
  2. 搬移字段(MoveField)。你的程序中,某個字段被其所駐類以外的另外一個類更多地用到。在目標類新建一個字段,修改源字段的全部用戶,令它們改用新字段。
  3. 提煉類(Extract Class)。某個類作了應該有兩個類作的事。創建一個新類,將相關的字段和函數從舊類搬移到新類。
  4. 將類內聯化(Inline Class)。某個類沒有作太多事情。將這個類的全部特性搬移到另外一個類中,而後移除原類。
  5. 隱藏「委託關係」(Hide Delegate)。客戶經過一個委託來調用另外一個對象。在服務類上創建客戶所需的全部函數,用以隱藏委託關係。
  6. 移除中間人(Remove Middle Man)。某個類作了過多的簡單委託動做。讓客戶直接調用受託類。
  7. 引入外加函數(Introduce ForeignMethod)。你須要爲提供服務的類增長一個函數,但你沒法修改這個類。在客戶類中創建一個函數,並以第一參數形式傳入一個服務類實例。
  8. 引入本地擴展(Introduce LocalExtension)。你須要爲服務類提供一些額外函數,但你沒法修改這個類。創建一個新類,使它包含這些額外函數。讓這個擴展品成爲源類的子類或包裝類。

10. 從新組織數據

  1. 自封裝字段(Self Encapsulate Field)。你直接訪問一個字段,但與字段之間的耦合關係逐漸變得笨拙。爲這個字段創建取值/設值函數,而且只以這些函數來訪問字段。
  2. 以對象取代數據值(Replace Data Value withObject)。你有一個數據項,須要與其餘數據和行爲一塊兒使用纔有意義。將數據項變成對象。
  3. 將值對象改成引用對象(Change Value toReference)。你從一個類衍生出許多彼此相等的實例,但願將它們替換爲同一個對象。將這個值對象變成引用對象。
  4. 將引用對象改成值對象(Change Reference to Value)。你有一個引用對象,很小且不可變,並且不易管理。將它變成一個值對象。
  5. 以對象取代數據(Replace Array withObject)。你有一個數組,其中的元素各自表明不一樣的東西。以對象替換數組,對於數組中的每一個元素,以一個字段來表示。
  6. 複製「被監視數據」(Duplicate ObservedData)。你有一些領域數據置身GUI控件中,而領域函數須要訪問這些數據。將該數據複製到一個領域對象中。創建一個Observe模式,用以同步領域對象和GUI對象內的重複數據。
  7. 將單向關聯改成雙向關聯(Change Unidirectional Association toBidirectional)。兩個類都須要使用對方特性,但其間只有一條單向連接。添加一個反向指針,並使修改函數可以同時更新兩條連接。
  8. 將雙向關聯改成單向關聯(Change Bidirectional Association toUnidirectional)。兩個類之間有雙向關聯,但其中一個類現在再也不須要另外一個類的特性。去除沒必要要的關聯。
  9. 以字面常量取代魔法數(Replace Magic Number with SymbolicConstant)。你有一個字面數值,帶有特別含義。創造一個常量,根據其意義爲它命名,並將上述的字面數值替換爲這個常量。
  10. 封裝字段(Encapsulate Field)。你的類中存在一個public字段。將它聲明爲private,並提供相應的訪問函數。
  11. 封裝集合(EncapsulateCollection)。有個函數返回一個集合。讓這個函數返回該集合的一個只讀副本,並在這個類中提供添加/移除集合元素的函數。
  12. 以數據類取代記錄(Replace Record with Data Class)。你須要面對傳統編程環境中的記錄結構。爲該記錄建立一個「啞」數據對象。
  13. 以類取代類型碼(Replace Type Code withClass)。類之中有一個數值類行碼,但它並不影響類的行爲。以一個新的類替換該數值類型碼。
  14. 以子類取代類型碼(Replace Type Code withSubclass)。你又一個不可變的類型碼,它會影響類的行爲。以子類取代這個類型碼。
  15. 以State/Strategy取代類型碼(Replace Type Code withState/Strategy)。你有一個類型碼,它會影響類的行爲,但你沒法經過繼承手法消除它。以狀態對象取代類型碼。
  16. 以字段取代子類(Replace Subclass withFields)。你的各個子類的惟一差異只在「返回常量數據」的函數身上。修改這些函數,使他麼返回超類中的某個(新增)字段,而後銷燬子類。

11. 簡化條件表達式

  1. 分解條件表達式(DecomposeConditional)。你有一個複雜的條件(if-then-else)語句。從if、then、else三分段落中分別提煉出獨立函數。
  2. 合併條件表達式(Consolidate ConditionalExpression)。你有一系列條件測試,都獲得相同結果。將這些測試合併爲一個條件表達式,並將這個條件表達式提煉成爲一個獨立函數。
  3. 合併重複的條件片斷(Consolidate Duplicate ConditionalFragments)。在條件表達式的每一個分支上有着相同的一段代碼。將這段重複的代碼搬移到條件表達式以外。
  4. 移除控制標記(Remove ControlFlag)。在一系列布爾表達式中,某個變量帶有「控制標記」的做用。以break語句或return語句取代控制標記。
  5. 以衛語句取代嵌套條件表達式(Replace nested Conditional with GuardClauses)。函數中的條件邏輯令人難以看清正常的執行路徑。使用衛語句表現全部的特殊狀況。
  6. 以多態取代條件表達式(Replace Conditional withPolymorphism)。你手上有個條件表達式,它根據對象類型的不一樣選擇不一樣的行爲。將這個條件表達式的每一個分支放進一個子類內的覆寫函數中,而後將原始函數聲明爲抽象函數。
  7. 引入Null對象(Introduce Null Object)。你須要再三檢查某對象是否爲null。將null值替換爲null對象。
  8. 引入斷言(Introduce Assertion)。某一段代碼須要對程序狀態作出某種假設。以斷言明確表現這種假設。

12. 簡化條件表達式

  1. 函數更名(Rename Method)。函數的名稱未能揭示函數的用途。修改函數的名稱。
  2. 添加參數(Add Parameter)。某個函數須要從調用端獲得更多信息。爲此函數添加一個對象參數,讓該對象帶進函數所需信息。
  3. 移除參數(Remove Parameter)。函數本體再也不須要某個參數。將該參數去除。
  4. 將查詢函數和修改函數分離(Separate Query from Modifier)。某個函數既返回對象狀態值,又修改對象狀態。創建兩個不一樣的函數,其中一個負責查詢,另外一個負責修改。
  5. 令函數攜帶參數(ParameterizeMethod)。若干函數作了相似的工做,但在函數本體中卻包含了不一樣的值。創建單一函數,以參數表達那些不一樣的值。
  6. 以明確函數取代參數(Replace Parameter with ExplicitMethods)。你有一個函數,其中徹底取決於參數值而採起不一樣行爲。針對該參數的每個可能值,創建一個獨立函數。
  7. 保持對象完整(Preserve WholeObject)。你從某個對象中取出若干值,將它們做爲某一次函數調用時的參數。改成傳遞整個對象。
  8. 以函數取代參數(Replace Parameter withMethods)。對象調用某個函數,並將所得結果做爲參數,傳遞給另外一個函數。而接受該參數的函數自己也可以調用前一個函數。讓參數接受者去除該項參數,並直接調用前一個函數。
  9. 引入參數對象(Introduce Parameter Object)。某些參數老是很天然地同時出現。以一個對象取代這些參數。
  10. 移除設值函數(Remove Setting Method)。類中的某個字段應該在對象建立時被設值,而後就再也不改變。去掉該字段的全部設值函數。
  11. 隱藏函數(Hide Method)。有一個函數,歷來沒有被其餘任何類用到。將這個函數修改成private。
  12. 以工廠函數取代構造函數(Replace Constructor with FactoryMethod)。你但願在建立對象時不只僅是作簡單的構建動做。將構建函數替換爲工廠函數。
  13. 封裝向下轉型(Encapsulate Downcast)。某個函數返回的對象,須要由函數調用者執行向下轉型。將向下轉型動做移到函數中。
  14. 以異常取代錯誤碼(Replace Error Code withException)。某個函數返回一個特定的代碼,用以表示某種錯誤狀況。改用異常。
  15. 以測試取代異常(Replace Exception withTest)。面對一個調用者能夠預先檢查的條件,你拋出了一個異常。修改調用者,使它在調用函數以前先作檢查。

13.處理歸納關係

  1. 字段上移(Pull Up Field)。兩個子類擁有相同的字段。將該字段移至超類。
  2. 函數上移(Pull Up Method)。有些函數,在各個子類中產生徹底相同的結果。將該函數移至超類。
  3. 構造函數本體上移(Pull Up ConstructorBody)。你在各個子類中擁有一些構造函數,他們的本體幾乎徹底一致。在超類中新建一個構造函數,並在子類構造函數中調用它。
  4. 函數下移(Push Down Method)。超類中的某個函數只與部分(而非所有)子類有關。將這個函數移到相關的那些子類去。
  5. 字段下移(Push Down Field)。超類中的某個字段只被部分(而非所有)子類用到。將這個字段移到須要它的那些子類去。
  6. 提煉子類(ExtractSubclass)。類中的某些特性只被某些(而非所有)實例用到。新建一個子類,將上面所說的那一部分特性移到子類中。
  7. 提煉超類(Extract Superclass)。兩個類有類似特性。爲這兩個類創建一個超類,將相同特性移至超類。
  8. 提煉接口(ExtractInterface)。若干客戶使用類接口中的同一子集,或者兩個類的接口有部分相同。將相同的子集提煉到一個獨立接口中。
  9. 摺疊繼承體系(Collapse Hierarchy)。超類和子類之間無太大差異。將它們合爲一體。
  10. 塑造模板函數(Form TemPlateMethod)。你有一些子類,其中相應的某些函數以相同順序執行相似的操做,但各個操做的細節上全部不一樣。將這些操做分別放進獨立函數中,並保持它們都有相同的簽名,因而原函數也就變得相同了。而後將原函數上移至超類。
  11. 以委託取代繼承(Replace Inheritance withDelegation)。某個子類只使用超類接口中的一部分,或是根本不須要繼承而來的數據。在子類中新建一個字段用以保存超類;調整子類函數令它改而委託超類;而後去掉二者之間的繼承關係。
  12. 以繼承取代委託(Replace Delegation withInheritance)。你在兩個類之間使用委託關係,並常常爲整個接口編寫許多極簡單的委託函數。讓委託類來繼承受託類。

14.大型重構**

  1. 梳理並分解繼承體系(Tease ApartInheritance)。某個繼承體系同時承擔兩項責任。創建兩個繼承體系,並經過委託關係讓其中一個能夠調用另外一個。
  2. 將過程化設計轉化爲對象設計(Convert Procedural Design toObjects)。你手上有一些傳統過程化風格的代碼。將數據記錄變成對象,將大塊的行爲分紅小塊,並將行爲移入相關對象之中。
  3. 將領域和表述/顯示分離(Separate Domain fromPresentation)。某些GUI類之中包含了領域邏輯。將領域邏輯分離出來,爲它們創建獨立的領域類。
  4. 提煉繼承體系(ExtractHierarchy)。你有某各種作了太多工做,其中一部分工做是以大量條件表達式完成的。創建繼承體系,以一個子類表示一種特殊狀況。

有幸看這本書的朋友 請反覆看 反覆看 反覆看!
但願看到這篇文章的人能有收穫,但願多年後翻看此篇文章的我能有更近一步的理解。 加油!
點贊 收藏 走一波。


高清pdf版本:連接: https://pan.baidu.com/s/1b69a2M 密碼: x9sf 請享用。

相關文章
相關標籤/搜索