重構—改善既有程式的設計(chapter 1)

Post on 15-Dec-2014

3276 Views

Category:

Education

4 Downloads

Preview:

Click to see full reader

DESCRIPTION

 

Transcript

RefactoringChapter 1: 重構,第一個案例

Kalin Chih

何時該考慮要重構 ?如果發現無法很方便地增加新 feature ,或修改邏輯作法 :

先找看看是否有一套可靠的測試機制 ?先重構程式再增加新 feature

重構 Step1: 了解案例案例 : 影片出租店程式

pirceCode: 決定影片類型、價錢daysRented: 決定租片金額、積點statement(): 列印租片紀錄清單與總金額、總積點

Example: chp1.movie_rental.v1.* Test Class: chp1.movie_rental.test.Test.java

重構 Step2: Extract 金額計算動機 : 過長的程式碼不易閱讀

Extract 過長的程式區塊 (Fine-grained)

作法 : Extract Method將 Customer.statement() 的內金額計算程式區塊,

Extract 成一個新 anmountFor()

Example : chp1.movie_rental.v2.extract_amount.*

重構前,有一套可靠的測試機制是很重要滴 ~

Rename 是值得的行為電腦可以理解和人寫出來的程式碼,但唯有寫出人類

容易理解的程式碼,才是優秀的程式員

重構 Step3: Move 金額計算動機 : 計算金額的程式碼為何不是放在 Rental 而是

放在 Customer?大多情況下, method 應該放在他所使用的 object

(class) 內金額計算應該放在 Rental 比放在 Customer 洽當

作法 : Move Method將 Customer.statement() 內的計算金額的程式碼搬

到 Rental

Example: chp1.movie_rental.v3.move_amount.*

重構 Step4: Move 點數計算動機 : 類似於金額計算,點數計算功能應該要屬於

Rental 比較洽當作法 :

將 Customer.statement() 內的點數計算程式碼搬到 Rental

Example: chp1.movie_rental.v4.move_frequentrenterpoints.*

重構 Step5: 移除 Temp Variables動機 : Temp Variable 可能會是個問題,助長程式碼過長

由於變數只是暫時的,所以能見度只在這個程式區塊。若在其他區塊也想要使用這個 Temp Variable ,便會驅使寫出更長的程式。

作法 : 使用 Query Method 取代 temp variable若使用 Query Method 取代 Temp Variable ,至少同一個 class 內

都能獲得這份資訊。getTotalCharge() 取代 totalAmountgetFrequentRenterPoints() 取代 frequentRenterPoints

Example: chp1.movie_rental.v5.replace_temp_with_query.*

New issue: 效能變差原本只要一個迴圈做到的事情,現在要跑三個迴圈才能做到

關於 Performance 這件事…Performance 應該是在最佳化效能時再來擔心,在

重構的階段先不用擔心這件事情Performance 應該是透過工具來檢測,肉眼是很難

看出來的回想重構的目的…

讓程式更容易被讀取讓程式更容易被擴充

The best way to optimize performance is to first write a well factored program, then optimize it

重構後…

現在可以透過這些 Query Methods 來取得資訊,而不需研究細節

重構 Step6: Move 金額計算、點數計算動機 : Rental 必須取得 Movie 才能運算金額。 ( 物件應該在自

己的資料上運算邏輯,而不是拿別人的資料來運算 )

考慮該邏輯該屬於哪個物件影片的價格邏輯是應該放到 Movie class 內來計算比較洽當

作法 : Move Method將 getCharge() 與 getFrequentRenterPoints() 移到 Movie

class Example: chp1.movie_rental.v6.move_selfobject

public class Rental {double getCharge() {

double result = 0;// 取得影片的出租價格switch (getMovie().getPriceCode()) {

// 普通片case Movie.REGULAR:

So Far So Good…寫了幾個新的 Query Methods 可以 reuse程式碼更容易閱讀

But! 計算金額的那段 switch 還是不利於將來計費邏輯的變動

考慮繼承…使用 Polymorphism 來取代 switch

問題來了,若之後 Movie 有其他類似 getCharge() 容易變動的邏輯,就很不利於維護

使用 Strategy Pattern

Strategy Pattern 封裝了會變動的演算法 (Price subclasses) ,所以不會影響使用演算法的程式(Movie)

重構 Step7: Replace Type Code with State/Strategy作法 (1/2):

Replace Type Code with State/Strategy: 將 type code behavior 搬移到 Strategy Pattern 讓金額計算的邏輯不再綁死在 Movie ,所以將金額計算的邏輯搬移到

Price 家族

Self Encapsulate Field 時機 : 當想要存取一個 super class 的 field ,卻在 sub class 改變

它的 value 作法 : 為 field 建立 setter 與 getter ,只用這些 methods 來存取

這個 field( 即使在同一個 class 內 )

Move Method Example: chp1.movie_rental.v7.strategy_pattern

重構 Step8: Replace Conditional with Polymorphism作法 (2/2):

Replace Conditional with Polymorphism: 在 sub Price classes 內建立 overriding method 來取代 super Price

class 的 switch將 super Price class 的 getCharge() 改為 abstract

Example: chp1.movie_rental.v7.strategy_pattern 方便 : 為了積點的預設值,所以不將 getFrequentRenterPoints() 也改成 abstract

public class NewReleasePrice extends Price {

double getCharge(int daysRented) {

return (double) daysRented * 3;}

public abstract class Price {double getCharge(int daysRented) {

double result = 0;switch (getPriceCode()) {

case Movie.NEW_RELEASE:result += (double) daysRented * 3;break;

}return result;

}

Final Class Diagram

重構後的好處讓程式碼更容易閱讀

Rename MethodExtract MethodMove Method

新的 Query Methods 可以 reuseReplace Temp with Query

將最容易變動的邏輯抽離出來Replace Type Code with State/StrategySelf Encapsulate FieldReplace Conditional with Polymorphism

Thank You!Slide & Example Code URL:

xxxx

top related