重構,改善既有代碼的設計

重構的定義:
是在不改變軟件可觀察行爲的前提下改善其內部結構
是在代碼寫好以後改進它的設計html

若是你發現本身須要爲程序添加一個特性,而代碼結構使你沒法很方便的達成目的,那就先重構那個程序。使特性的添加比較容易進行,而後再添加特性。程序員

重構第一步:好的測試是重構的根本。爲即將修改的代碼創建一組可靠的測試環境。這些測試必須有自我檢驗的能力。算法

最好不要在另外一個對象的屬性基礎上運用switch語句。若是不得不使用,也應該在對象本身的數據上使用,而不是在別人的數據上使用。編程

咱們有數種影片類型,他們以不一樣的方式回答相同的問題。這聽起來很像子類的工做。咱們能夠創建Movie的三個子類。每一個都有本身的計費方法。
上面想法的問題是:一部影片能夠在生命週期內修改本身的分類,一個對象卻不能在生命週期內修改本身所屬的類。
這個時候就可使用狀態(State)模式。設計模式

狀態模式:
不只用類能夠表示對象,也能夠用類表示狀態。咱們能夠切換不一樣的類來表示不一樣的狀態。
若是在一個類中有幾種狀態,在方法的調用中就須要判斷不一樣狀態下的不一樣作法。而在這個時候就可使用狀態模式。用不一樣的類表示不一樣的狀態,咱們只須要取得狀態的委託調用他們就行了。不用關心他們的具體實現是什麼。性能優化

推薦:儘可能少用 if-else。
若是超過3層的if-else,請使用狀態模式。session

重構原則
重構的名詞概念:對軟件內部結構的一種調整,在不改變軟件可觀察行爲的前提下,提升其可理解性,下降其修改爲本。app

和性能優化的區別:性能優化每每使代碼難以理解,但爲了性能 你不得不那麼作。函數

兩頂帽子:
「添加新功能」和「重構」
添加新功能時,不該該修改既有代碼,只管添加新功能。
重構時就不能添加功能,只管改進程序結構。工具

重構很像是整理代碼,你所作的就是讓全部東西回到應處的位置上。

完成一樣一件事,設計不良的代碼每每須要更多的代碼,由於代碼在不一樣的地方使用徹底相同的語句作一樣的事情。所以改進設計的一個重要方向就是消除重複代碼。這個動做的重要性在於方便將來的修改。

重構使軟件更容易理解,在此基礎上,程序員要修改某段代碼的成本就會很低。

重構能夠幫忙找到bug.在對代碼重構的同時,能夠深刻理解代碼的作爲,並恰到好處的把新的理解反饋回去。而後經過搞清楚本身所作的一些假設,找出隱藏的bug。

良好的設計是快速開發的根本。重構就是把代碼進行從新設計,防止系統腐敗變質,提升設計質量。

什麼時候重構

不須要特別撥出時間進行重構。重構應該隨時隨地進行。之因此重構,是由於你想作什麼事,而重構能幫你幫那件事作好。

三次法則:
第一次作某件事只管去作
第二次作相似的事情仍是能夠去作;
第三次仍是作一樣的事情,就應該重構。

1.添加功能時重構
若是代碼的設計沒法幫助我輕鬆添加我所須要的特性

2.修補錯誤時重構
若是收到一份錯誤報告,這就是須要重構的信號,由於代碼沒有清晰到你能夠一眼看出bug。

3.複審代碼時重構
經過別人的眼光看本身的代碼,接納別人提出的有效意見

程序有兩面價值:「今天爲你作什麼」和「明天爲你作什麼」。大多數時候,咱們只關注本身今天想要程序作什麼。對於今天的工做,我瞭解的很充分;對於明天的工做,我瞭解的不夠充分。但若是我純粹只是爲今天工做,明天我將沒法工做。

重構是一條擺脫困境的道路。若是你發現昨天的決定已經不適合今天的狀況,放心改變這個決定就好。

間接層的應用

容許邏輯共享

若是重構手法改變了已發佈接口,你必須同時維護新舊兩個接口,直到全部用戶都有時間對這個變化作出反應。
能夠這麼作:讓舊接口調用新接口。將舊接口標記爲deprecated。

重構與設計

事先作好設計能夠節省返工的高昂成本

有一種觀點認爲:重構能夠取代預先設計。先按最初想法開始編碼,讓代碼有效運行,而後再進行重構。

在有了預先設計,而後開始實現的時候,你會對問題的理解逐漸加深,可能會察覺你的解決方案和當初設想的有些不一樣。不過只要有重構這把利器在手,就不成問題,能夠用重構把軟件設計趨於完美。

不少時候的程序設計在提升靈活性的同時也使得程序的複雜度和維護難度大大提升。

有了重構,你就能夠經過一條不一樣的途徑來應對變化帶來的風險。沒必要預先思考前述所謂的靈活方案--一旦須要他,你總有足夠的信心去重構。當下只管建造可運行的最簡化系統,至於靈活而複雜的設計,多數時候你都不會須要它。

重構與性能

雖然重構可能使軟件運行更慢,但它使軟件的性能優化更容易。咱們能夠首先寫出可調的軟件,而後調整它以求得到足夠的速度。

咱們一視同仁的優化全部的代碼,但是高頻率執行的代碼可能只有10%。

在性能優化階段,首先應該用一個度量工具來監控程序的運行,讓他告訴你哪些地方消耗更多的時間和空間。而後把這些性能熱點所在的代碼進行優化。

一個構建良好的代碼能夠幫助你進行性能優化。
1.更快速的添加功能,有更多的時間用在性能問題上。
2.有較細的粒度分析性能,度量工具能夠把你帶到範圍較小的代碼段落中。

何時重構

Duplicated Code

Long Method

程序愈長愈難理解。讓小函數容易理解的關鍵在於一個好名字。讀者能夠經過名字理解函數的做用,根本不用去看其中寫了些什麼。

若是函數內有大量的參數和臨時變量,他們會對你的函數提煉造成阻礙。

1尋找註釋

2條件表達式和循環經常是提煉的信號.將循環和其中的代碼提煉到一個獨立函數中

Large Class

Long Parameter List

將對象傳遞給函數

Divergent Change (發散式變化)
指的是「一個類受多種變化的影響」
一旦代碼須要修改,咱們但願可以跳到某一點,只在該處修改。若是不能這樣,就是要改變的時候了。

針對外界某一變化的全部相應修改,都只應該反應在單一類中,而這個類內的全部內容都應該反應此變化。找出某特色緣由形成的全部變化,把他們提煉到另外一個類中。

Shotgun Surgery
指的是「一種變化引起多個類相應修改」
若是須要修改的代碼散佈四處,就把這些須要修改的代碼放進同一個類中。
這種狀況下,咱們整理代碼,使「外界變化」與「須要修改的類」趨於一一對應。

Feature Envy(依戀情結)

對象技術:將數據和對數據的操做行爲包裝在一塊兒的技術

一個函數中用到不少其餘類的內容

Data Clumps(數據泥團)

兩個類中相同的字段、許多函數簽名中相同的參數。這些老是綁在一塊兒的出現的數據真應該擁有屬於他們本身的對象。

沒必要在乎data clumps只用上新對象的一部分字段,只要以新對象取代兩個(或更對)字段,就值回票價了。

在擁有新對象以後,你就能夠着手尋找Feature Envy。

Primitive Obsession(基本類型偏執)

將本來單獨存在的數據值替換爲對象。

Switch Statements

面向對象的程序要少用switch或(case)語句。從本質上說,switch語句的問題在於重複。面向對象的多態概念可爲此帶來優雅的解決辦法

1.將switch語句提煉到一個獨立函數中
2.將這個函數搬移到須要多態性的那個類中
這個時候你就能夠決定是否使用 replace type code with subclasses 或者 replace type code with state/strategy
而後你就能夠運用replace conditional with polymorphism

Parallel Inheritance Hierarchies(平行繼承體系)

Shotgun Surgery的特殊狀況
每當爲一個類增長子類,必須也須要爲另外一個類相應增長子類的時候

消除這種重複性的策略:讓一個繼承體系的實例引用另外一個繼承體系的實例。

Lazy Class

一個類的所得不值其身價,就讓他消失。

Speculative Generality(誇誇其談將來性)

Temporary Field(使人迷惑的暫時字段)

某個字段僅爲某些特殊狀況存在。
把這樣的字段和相關的代碼放進一個新對象裏面。提煉後的新對象將是一個函數對象。

Message Chains (過分耦合的消息鏈)

Middle Man

對象的基本特徵就是封裝--對外部世界隱藏其內部細節,封裝每每伴隨着委託。
若是某個類接口一半的函數都交給其餘類,就是過分使用委託。

Inappropriate Intimacy(狎暱關係)

Aliternative Classes with Different Interfaces(殊途同歸的類)

Incomplete Library Class(不完美的庫類)

Data Class(純稚的數據類)
不要有public字段
恰當的封裝容器類的字段
對於不應被其餘類修改的字段,使用readonly

找出這些取值/設值函數被其餘類運用的地點,嘗試搬移叨叨Data Class類中。

Refused Bequest(被拒絕的饋贈)

Comments(過多的註釋)

若是代碼有着長長的註釋,一般是由於代碼很糟糕。

若是須要註釋來解釋一塊代碼,試試 Extract Method.
若是函數已經提煉,仍是須要註釋,試試Rename Method
當你感受須要註釋時,請先嚐試重構。

構築測試體系

自測試代碼的價值:

修復錯誤一般是比較快的,但找出錯誤倒是噩夢一場。
"類應該包含它們本身的測試代碼"

一套測試就是一個強大的bug偵測器,可以大大縮減查找bug所須要的時間。

撰寫測試代碼的最有用時機是在開始編程以前。

在重構以前先確保代碼可以進行自我測試。

頻繁的進行測試。每次編譯請把測試頁考慮進去,天天至少執行每一個測試一次。

編寫測試代碼時, 一開始先讓它們失敗,測試一下測試代碼的機制能夠運行。

程序員的好朋友 :單元測試。
單元測試是高度局部化的東西,每一個測試類都屬於單一包。
功能測試用來保證軟件能夠正常運做。

每當收到一個bug報告,請先寫一個單元測試來暴露這個bug

觀察類該作的全部事情,而後針對任何功能的任何一種可能失敗狀況,進行測試

測試的要訣是:測試你最擔憂出錯的部分。

每次測試都調用setUp()和tearDown() 是很重要的,由於這樣才能保證測試之間彼此隔離。也就是說咱們能夠按任意順序進行他們,不會對他們的結果形成任何影響。

測試的一項重要技巧就是「尋找邊界條件」

考慮可能出錯的邊界條件,把測試火力集中在那兒。

當事情被認爲會出錯時,別忘了檢查是否拋出了預期的異常。

單元測試的優勢:
1.便於後期重構
2.優化設計
3.文檔記錄
4.具備迴歸性
5.帶來自信
6.快速反饋
7.節省時間

設計模式,爲重構行爲提供了目標。模式是你但願達到的目標,重構則是到達之路 。

重構手法:

1.Extract Method.
好處:函數複用的機會更大
使高層函數更好閱讀
函數的覆寫也要容易

只有當你能給小函數很好的命名時,他們才能真正起做用,因此你須要在函數名稱上下功夫。

1.創造一個新函數,以這個函數「作什麼」來命名。
2.檢查提煉出的代碼,是否引用了做用域限於原函數的變量
3.檢查是否有「僅用於被提煉代碼段」的臨時變量。若是有,在目標函數中將他們聲明爲臨時變量。
4.檢查被提煉代碼段,是否有任何局部變量的值被他改變。若是有,就把提煉代碼端處理爲一個查詢,並將結果賦值給相關變量

若是很難這麼作,或者被修改的變量不止一個,就使用 Split Temporary Variable。

將被提煉代碼段中須要讀取的局部變量,當作參數傳給目標函數。

replace Method with Method Object[http://www.javashuo.com/article/p-psgohyah-ex.html]

Inline Temp(內聯函數):
現象:一個函數的本體與名稱一樣清晰易懂。
作法:在函數調用點直接使用函數其中的代碼,而後移除該函數。

replace temp with query(以查詢取代臨時變量)

臨時變量保存某一表達式的運算結果。
將表達式提煉到一個函數,返回表達式結果,賦值給臨時變量。
好處:能夠在類中的各個地方獲取計算以後的值。避免寫出超長函數

若是你想替換的變量是用來收集結果的(例如循環中的累加值),就須要將某些程序邏輯(例如循環)複製到查詢函數中去。

對於如此帶來的性能問題,你能夠如今無論他,由於它有可能不會帶來任何影響。若是有影響,就在優化時期解決它。代碼組織良好,每每可以發現更有效的優化方案。

簡單狀況:
臨時變量只被賦值一次

1.將「對該臨時變量賦值」之語句的等號右側部分提煉到一個獨立函數中。
2.確保提煉出來的函數不修改任何對象內容。若是有,就對他進行 Separate Query from Modifler
3.在該臨時變量身上實施inline temp

Introduce Explaining Variable (引入解釋性變量)

將一個複雜的表達式的結果放進一個臨時變量,以此變量名稱解釋表達式用途。

Split Temporary Variable (分解臨時變量)

某個臨時變量被賦值超過一次, 它既不是循環變量,也不被用於收集計算結果。
針對每次賦值,創造一個獨立、對應的臨時變量

若是稍後之賦值語句是[i = i+某表達式],就意味着這是個結果收集變量。這種變量的做用一般是累加,字符串接合,寫入流或者向集合添加元素。

Remove Assignments to Parameters(移除對參數的賦值)
以一個臨時變量取代該參數的位置

把參數傳遞當作按值傳遞來作。

replace Method with Method Object

Substitute Algorithm(替換算法 )

Move Field(搬移字段)

Introduce Local Extension(引入本地擴展)

從新組織數據

1.自封裝字段
2.replace data value with object(以對象取代數據值)
3.change value to reference (將值對象改成引用對象)
但願給一個值對象加入可修改的數據,並確保給一個對象的修改都能影響到全部引用此一對象的地方

4.replace array with object

須要圖書PDF的能夠留言

相關文章
相關標籤/搜索