Top Banner
Refactoring Chapter 1: 重重 重重 重重重 ,一 Kalin Chih
19

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

Dec 15, 2014

Download

Education

Chris Huang

 
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: 重構—改善既有程式的設計(chapter 1)

RefactoringChapter 1: 重構,第一個案例

Kalin Chih

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

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

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

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

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

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

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

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

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

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

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

Extract 成一個新 anmountFor()

Example : chp1.movie_rental.v2.extract_amount.*

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

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

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

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

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

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

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

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

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

到 Rental

Example: chp1.movie_rental.v3.move_amount.*

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

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

Rental 比較洽當作法 :

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

Example: chp1.movie_rental.v4.move_frequentrenterpoints.*

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

重構 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: 效能變差原本只要一個迴圈做到的事情,現在要跑三個迴圈才能做到

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

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

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

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

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

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

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

重構後…

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

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

重構 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:

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

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

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

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

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

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

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

使用 Strategy Pattern

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

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

重構 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

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

重構 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;

}

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

Final Class Diagram

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

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

Rename MethodExtract MethodMove Method

新的 Query Methods 可以 reuseReplace Temp with Query

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

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

Thank You!Slide & Example Code URL:

xxxx