第一個案例:算法
重構的第一步:爲即將改變的代碼創建一組可靠的測試環境。數據庫
public class Movie { public static final int CHILDRENS = 2; public static final int REGULAR = 0; public static final int NEW_RELEASE = 1; private String _title; private int _priceCode; public Movie(String title, int priceCode) { _title = title; _priceCode = priceCode; } public String getTitle() { return _title; } public void setTitle(String _title) { this._title = _title; } public int getPriceCode() { return _priceCode; } public void setPriceCode(int _priceCode) { this._priceCode = _priceCode; } }
public class Rental { private Movie _movie; private int _daysRented; public Rental(Movie _movie, int _daysRented) { this._movie = _movie; this._daysRented = _daysRented; } public Movie getMovie() { return _movie; } public int getDaysRented() { return _daysRented; } }
public class Cunstomer { private String _name; private Vector _rentals = new Vector(); public Cunstomer(String _name) { this._name = _name; } public void addRental(Rental arg) { _rentals.addElement(arg); } public String getName() { return _name; } public String statement() { double totalAmount = 0; int frequentRenterPoints = 0; Enumeration rentals = _rentals.elements(); String result = "Rental Record for " + getName() + "\n"; while (rentals.hasMoreElements()) { double thisAmount = 0; Rental each = (Rental) rentals.nextElement(); switch ((each.getMovie().getPriceCode())) { case Movie.REGULAR: thisAmount += 2; if (each.getDaysRented() > 2) thisAmount += (each.getDaysRented() - 2) * 1.5; break; case Movie.NEW_RELEASE: thisAmount += each.getDaysRented() * 3; break; case Movie.CHILDRENS: thisAmount += 1.5; if (each.getDaysRented() > 3) thisAmount += (each.getDaysRented() - 3) * 1.5; break; } frequentRenterPoints++; if((each.getMovie().getPriceCode()==Movie.NEW_RELEASE)&&each.getDaysRented()>1) frequentRenterPoints++; result += "\t" + each.getMovie().getTitle() + "\t" + String.valueOf(thisAmount) + "\n"; totalAmount += thisAmount; } result += "Amount owed is" + String.valueOf(totalAmount) + "\n"; result += "You earned " + String.valueOf(frequentRenterPoints) + " frequent renter points"; return result; } }
如今須要重構的就是這個statement:編程
重構這個statement分爲如下幾步:數組
第一步:switch語句性能優化
我用idea重構功能,他並自動生成的方法,thisAmount也是做爲入參傳進來的,看起來好像沒多大差,至少看到這我是沒看出來差異或者特殊的用意的。數據結構
第二步:搬移「金額計算」的代碼ide
觀察amountFor()時,發現這個函數使用了Rental類的信息,沒有使用來自Customer類的信息。絕大多數狀況下,函數應該放在它所使用的數據的所屬對象內,因此amountFor()應該移動到Rental類去。而後找出舊函數的引用點並修改成引用新函數(全局搜索一下看都有哪些引用)。去掉舊函數。而後進行測試。函數
有時候會保留舊函數,而後讓它調用新函數。好比說舊函數是public的函數,而又不想修改其餘類的接口,這就是一種頗有效的方法。性能
移動以後,函數會有微調,到Rental類的方法,不須要參數了。單元測試
第三步:改完以後,thisAmount就變得多餘了,由於他接受的是each.getCharge()的結果,而且不會發生變化,因此能夠去掉thisAmount這個局部變量。
第四步:提煉「常客積分計算」代碼
首先,積分計算應該放在Rental類。而後,再看一下局部變量。each,能夠被看成參數傳入新函數;另外一個臨時變量是frequentRenterPoints,它在被使用前有初始化值,但提煉出來的函數並無讀取該值,因此咱們不須要把它看成參數傳進去,只須要把新函數的返回值累加上去就好了。
第五步:去除臨時變量。
臨時變量多是個問題,他們使函數變得冗長而複雜。這個例子有2個臨時變量totalAmount和frequentRentalPoints,他們都是用來從Rental對象中得到某個總量,能夠利用查詢函數來取代totalAmount和frequentRentalPoints這2個臨時變量。這樣類中任何函數均可以調用這個查詢函數了。
第六步:
假如,須要修改影片分類規則,費用計算方式和常客積分計算方式有待肯定。這個時候盲目重構,確定是不合適的。
這個時候能夠運用多態取代與價格相關的條件邏輯。
首先,是switch,最好不要再另外一個對象的屬性基礎上運用switch語句,若是不得不使用,也應該在對象本身的數據上使用,而不是在別人的數據上使用。
也就是getCharge()應該從Rental類移動到Movie類中。這個時候想讓方法可使用,必須把租期長度做爲參數傳遞進去。租期長度來自Rental對象。計算費用時須要2項數據:租期長度和影片類型。選擇把租期長度傳給Movie對象而不是將影片類型傳給Rental對象,是由於影片類型可能會發生變化,因此選擇在Movie對象內計算費用。
第7步:繼承
加入這一層間接性,能夠在Price對象內進行子類化動做,能夠在任何須要時刻修改價格。
爲了引入State模式,首先運用Replace Type Code with State/Strategy,將與類型相關的行爲搬移到State模式內。而後運用Move Method講switch語句移動到Price類,最後運用Replace Conditional with Palymorphism去掉switch語句。
運用Replace Type Code with State/Strategy:
第一步驟,針對類型代碼使用SelfEncapsulate Field,確保任什麼時候候都經過取值函數和設置函數來訪問類型代碼。多數訪問操做來自其餘類,他們已經在使用取值函數,但構造函數仍然直接訪問價格代碼,能夠在Movie構造函數中,用一個設置函數來代替直接訪問價格,而後編譯測試,確保沒有破壞任何東西。新建一個Price類,並在其中提供類型相關的行爲,即在Price類中加入一個抽象函數,並在全部子類中加上對應的具體函數。
第二步:修改Movie類中的「價格代號」訪問函數(取值函數/設置函數),讓他們使用新類。在Movie類內保存一個Price對象,而再也不保存一個_priceCode變量,修改訪問函數。而後從新編譯測試。
Move Method:對getCharge()實施Move Method。
搬移以後,運用Replace Conditional with Palymorphism:一次取出一個case分支,在相應的類創建一個覆蓋函數。這個函數覆蓋了父類中的case分支,父類中的代碼先不動,在取出下一個case分支,一次處理,並編譯測試(注意確保執行的是子類的)。處理完全部的case分支以後,把P.getCharge()聲明爲abstract。再運用一樣的手法處理getFrequentRenterPoints().可是這個方法不須要把超類函數聲明爲abstract,只須要爲新片類型增長一個覆蓋函數,並在超類留下一個已定義的函數,使它成爲一種默認行爲。
引入State模式,能夠作到,若是要修改任何與價格有關的行爲,或是添加新的訂價標準,或是加入其它取決於價格的行爲,程序的修改會容易不少。
通過以上重構以後,代碼變成了以下的樣子:
public abstract class Price { abstract int getPriceCode(); abstract double getCharge(int daysRented); public int getFrequentRenterPoints(int daysRented) { return 1; } }
public class RegularPrice extends Price { @Override int getPriceCode() { return Movie.REGULAR; } public double getCharge(int daysRented) { double thisAmount = 2; if (daysRented > 2) thisAmount += (daysRented - 2) * 1.5; return thisAmount; } }
public class ChildrensPrice extends Price { @Override int getPriceCode() { return Movie.CHILDRENS; } public double getCharge(int daysRented) { double thisAmount = 1.5; if (daysRented > 3) thisAmount += (daysRented - 3) * 1.5; return thisAmount; } }
public class NewReleasePrice extends Price { @Override int getPriceCode() { return Movie.NEW_RELEASE; } public double getCharge(int daysRented) { return daysRented * 3; } public int getFrequentRenterPoints(int daysRented) { return daysRented > 1 ? 2 : 1; } }
public class Rental { private Movie _movie; private int _daysRented; public Rental(Movie _movie, int _daysRented) { this._movie = _movie; this._daysRented = _daysRented; } public Movie getMovie() { return _movie; } public int getDaysRented() { return _daysRented; } public double getCharge() { return _movie.getCharge(_daysRented); } public int getFrequentRenterPoints() { return _movie.getFrequentRenterPoints(_daysRented); } }
public class Movie { public static final int CHILDRENS = 2; public static final int REGULAR = 0; public static final int NEW_RELEASE = 1; private String _title; private Price _price; public Movie(String title, int priceCode) { _title = title; setPriceCode(priceCode); } public String getTitle() { return _title; } public void setTitle(String _title) { this._title = _title; } public int getPriceCode() { return _price.getPriceCode(); } public void setPriceCode(int args) { switch (args) { case REGULAR: _price = new RegularPrice(); break; case CHILDRENS: _price = new ChildrensPrice(); break; case NEW_RELEASE: _price = new NewReleasePrice(); break; default: throw new IllegalArgumentException("Incorrent Price Code"); } } public double getCharge(int daysRented) { return _price.getCharge(daysRented); } public int getFrequentRenterPoints(int daysRented) { return _price.getFrequentRenterPoints(daysRented); } }
public class Cunstomer { private String _name; private Vector _rentals = new Vector(); public Cunstomer(String _name) { this._name = _name; } public void addRental(Rental arg) { _rentals.addElement(arg); } public String getName() { return _name; } public String statement() { Enumeration rentals = _rentals.elements(); String result = "Rental Record for " + getName() + "\n"; while (rentals.hasMoreElements()) { Rental each = (Rental) rentals.nextElement(); result += "\t" + each.getMovie().getTitle() + "\t" + String.valueOf(amountFor(each)) + "\n"; } result += "Amount owed is" + String.valueOf(getTotalCharge()) + "\n"; result += "You earned " + String.valueOf(getTotalFrequentRenterPoints()) + " frequent renter points"; return result; } private int getTotalFrequentRenterPoints() { int frequentRenterPoints = 0; Enumeration rentals = _rentals.elements(); while (rentals.hasMoreElements()) { Rental each = (Rental) rentals.nextElement(); frequentRenterPoints++; frequentRenterPoints = each.getFrequentRenterPoints(); } return frequentRenterPoints; } private double getTotalCharge() { double totalAmount = 0; Enumeration rentals = _rentals.elements(); while (rentals.hasMoreElements()) { Rental each = (Rental) rentals.nextElement(); totalAmount += amountFor(each); } return totalAmount; } private double amountFor(Rental rental) { return rental.getCharge(); } }
能夠試試把最開始的代碼考到idea中,而後看看若是是你,你會怎麼優化,而後按提示優化下,再跟答案比一下,最後對比一下本身的答案,仍是挺有意思的,這個就是第一章的內容。
第二章:重構原則
重構的目的是使軟件更容易被理解和修改。重構不會改變軟件可觀察的行爲,重構以後軟件功能一如以往。
與之造成對比的是性能優化。和重構同樣,性能優化一般不會改變組建的行爲(除了執行速度),以後改變內部結構。可是二者出發點不一樣:性能優化每每使代碼較爲難理解,但爲了獲得所須要的性能不得不那麼作。
爲什麼重構:
1.重構改進軟件設計,好比,消除重複代碼,肯定全部事物和行爲在代碼中只表述一次,方面將來代碼的修改。
2.重構使軟件更容易理解:在重構上花一點點時間,就可讓代碼更好地表達本身的用途,即「準確說出我所要的」。並且,還能夠利用重構來幫助本身協助理解不熟悉的代碼。真正動手修改代碼,讓它更好地反映出個人理解,而後從新執行,看它是否正常運行,檢驗本身的理解是否正確。隨着代碼的簡潔,就能夠看到之前看不到的設計層面的東西。
3.重構幫助找bug:
4.重構提升編程速度
什麼時候重構:
重構不是一件應該特別撥出事件作的事情,重構應該隨時進行,不該該爲了重構而重構。之因此重構,是由於想作別的事情,而重構能夠幫助把那些事作好。
三次法則:第一次作某件事時只管去作;第二次作相似的事會產生反感,但不管如何仍是能夠去作;第三次在作相似的事,就應該重構。事不過三,三則重構。
1.添加功能時重構
2.修補錯誤時重構:調試過程當中運用重構,讓代碼更具備可讀性,由於代碼還不夠清晰,沒有讓本身一眼看出bug。
3.複審代碼時重構:開始重構前能夠先閱讀代碼,獲得必定程度的理解,並提出一些建議。一旦想到一些點子,考慮是否能夠經過重構當即輕鬆地實現它們。多作幾回重構,代碼會變得更清楚,提出更多恰當的建議,能夠得到更高層次的認識。重構還能夠幫助代碼複審工做獲得更具體的認識,不只得到建議,並且其中許多建議可以理解實現,從實現中獲得成就感。
間接層和重構:
間接層的價值:容許邏輯共享;分開解釋意圖和實現;隔離變化;封裝條件邏輯
找出一個缺少「間接層利益」之處,在不修改現有行爲的前提下,爲它加入一個間接層,得到一個更有價值的程序,提升程序質量,讓本身再明天受益。
找出不值得的間接層,將它拿掉。這種間接層常以中介函數形式出現,它多是個組件,你原本指望在不一樣地方共享它或者讓它表現出多態性,最終卻只在一處用到。
重構的難題:
1.數據庫:重構常常出問題的一個領域就是數據庫。第一,絕大多數商用程序都和背後的數據庫結構緊密耦合在一塊兒;第二,數據遷移。就算將系統分層,將數據庫結構和對象模型間依賴降至最低,但數據庫結構改變仍是讓你不得不遷移全部數據,這是漫長而煩瑣的工做。
在非對象數據庫中,解決這個問題的辦法之一就是:在對象模型和數據庫模型之間插入一個分隔層,這就能夠隔離兩個模型各自的變化。升級某一模型是,只需升級上述的分離層便可。這樣的分割層會增長系統複雜度,但能夠帶來很大的靈活度。若是同時擁有多個數據庫,或若是數據庫模型較爲複雜使你難以控制,那麼就是不進行重構,這分隔層也是很重要的。
對開發者而言,對象數據庫既有幫助也有妨礙。自行完成遷移時,必須留神類中的數據結構變化,能夠放心把類的行爲轉移過去,可是轉移字段時必須格外當心,數據還沒有被轉移前就得先運用訪問函數形成「數據已經轉移」的假象。一旦肯定知道數據應該放在何處,就能夠一次性將數據遷移過去。這是惟一須要修改的只有訪問函數,能夠下降錯誤風險。
2.修改接口:如何面對那些必須修改「已發佈接口」的重構手法?儘可能讓舊接口調用新接口。但這樣會使接口變得複雜,還有另外一個選擇,能不發佈接口的時候,儘可能不要發佈接口。
什麼時候不應重構:現行代碼不能正常運行,盡是錯誤,重構不如重寫來的簡單。重構以前,代碼必須在大部分狀況下正常運行。項目接近最後期限,避免重構。
重構與性能:除了對性能有嚴格要求的實時系統,其餘任何狀況下「編寫快速軟件」的祕密就是,首先寫出可調的軟件,而後調整它以求得到足夠速度。
第三章:代碼的壞味道
1.重複代碼
2.過長函數
3.過大的類
4.過長參數列
5.發散式變化
6.霰(xian,第四聲)彈式修改
7.依戀情節
函數對某個類的興趣高過對本身所處類的興趣,這種孺慕之情一般就是數據。把這個函數移到另外一個地方,使用Move Method,有時候函數中只有一部分受這種依戀之苦,就應該使用Extract Method提煉到獨立的函數,在使用move method帶它去他該去的地方。若是一個函數用到幾個類的功能,判斷哪一個類擁有最多被此函數使用的數據,把該函數移到那個類,若是先以Extract Method將這個函數分解爲數個較小函數並分別置放於不一樣地點,上述步驟就更容易完成了。將老是一塊兒變化的東西放在一起。若是例外出現,就搬移那些行爲,保持變化只在一地發生。Stragegy和Visitor使得能夠輕鬆修改函數行爲,但多一層間接性的代價。
8.數據泥團
9.基本類型偏執
10.switch語句
11.平行繼承體系
12.冗餘類
13.誇誇其談將來性
14.使人迷惑的暫時字段
15.過分耦合的消息鏈。
16.中間人
17.狎暱關係
18.殊途同歸的類
19.不完美的庫類
20.純粹的數據類
21.被拒絕的遺贈
22.過多的註釋
第4章:構建測試體系
自動化的單元測試:test suit
每當收到bug報告,請先寫一個單元測試來暴露bug
繼續添加更多測試:觀察類該作的全部事情,而後針對任何一項功能的任何一種可能失敗狀況,進行測試。
測試的一項重要技巧就是「尋找邊界條件」。「尋找邊界條件」也包括尋找特殊的,可能致使測試失敗的狀況。
當事情被認爲應該會出錯,要記得檢查是否拋出了預期的異常。
把測試集中在可能出錯的地方。「花合理時間抓出大多數bug」要好過「窮盡一輩子抓出全部bug」。
構建一個良好的bug檢測器並常常運行它,對任何開發工做都將大有裨益,而且是重構的前提。
第5章:重構列表
第6章:從新組織函數
1.提煉函數
有局部變量時:
局部變量最簡單的狀況是:被提煉代碼段只是讀取這些變量的值,並不修改它們。這種狀況下我能夠簡單地將它們看成參數傳給目標函數。
若是局部變量是個對象,而被提取代碼調用了對該對象形成修改的函數,也能夠如法炮製,只須要將這個對象做爲參數傳遞給目標函數便可。只有在被提煉代碼真的對一個局部變量賦值的狀況下,才必須採起其餘措施。
2.內聯函數
3.內聯臨時變量
有一個臨時變量,只被簡單表達式賦值一次,而它妨礙了其餘重構手法。就須要內聯化。
4.以查詢取代臨時變量
5.引入解釋性變量
將複雜表達式(或其中一部分)的結果放進一個臨時變量,以此變量名稱來解釋表達式用途。
6.分解臨時變量
程序中某個臨時變量被賦值超過一次,它既不是循環變量,也不被用於收集計算結果,針對每次賦值,創造一個獨立、對應的臨時變量。
7.移除對參數的賦值
(這個我不是很能理解和吸取)
8.以函數對象取代函數
有一個大型函數,其中對局部變量的使用使你沒法採用Extract Method。將這個函數放進一個單獨對象中,如此一來局部變量就成了對象內的字段。而後能夠在同一個對象中將這個大型函數分解爲多個小型函數。
它帶來的好處是:能夠輕鬆地對compute()函數採起Extract Method,沒必要擔憂參數傳遞問題。
9.替換算法
第七章:在對象之間搬移特性
一、搬移函數:
2.搬椅字段
3.提煉類
4.將類內聯化
5.隱藏委託關係
6.移除中間人
7.引入外加函數
8.引入本地擴展
這個根本沒看懂。
第八章:
1.自封裝字段
2.以對象取代數據值
3.將值對象改成引用對象
4.將引用對象改成值引用
5.以對象代替數組
6.複製被監視的數據
7.將單向關聯改成雙向關聯
8.將雙向關聯改成單向關聯
7和8都沒怎麼懂
9.以字面常量取代魔法數
10.封裝字段
11.封裝集合
12.以數據類取代記錄
13.以類取代類型碼
(未完待續)