### Replace Temp with Query(以查詢取代臨時變量)
你的程序以一個臨時變量(temp)保存某一表達式的運算結果。
將這個表達式提煉到一個獨立函數(譯注:所謂查詢式,query)中。將這個臨時變量的所有「被引用點」替換為「對新函數的調用」。新函數可被其他函數使用。
~~~
double basePrice = _quantity * _itemPrice;
if (basePrice > 1000)
return basePrice * 0.95;
else
return basePrice * 0.98;
~~~
=>
~~~
if (basePrice() > 1000)
return basePrice() * 0.95;
else
return basePrice() * 0.98;
...
double basePrice() {
return _quantity * _itemPrice;
}
~~~
**動機(Motivation)**
臨時變量的問題在于:它們是暫時的,而且只能在所屬函數內使用。由于臨時變量只有在所屬函數內才可見,所以它們會驅使你寫出更長的函數,因為只有這樣你才能訪問到想要訪問的臨時變量。如果把臨時變量替換為一個查詢式(query method),那么同一個class中的所有函數都將可以獲得這份信息。這將帶給你極大幫助,使你能夠為這個編寫更清晰的代碼。
Replace Temp with Query往往是你運用Extract Method 之前必不可少的一個步驟。局部變量會使代碼難以被提煉,所以你應該盡可能把它們替換為查詢式。
這個重構手法較為直率的情況就是:臨時變量只被賦值一次,或者賦值給臨時變量的表達式不受其他條件影響。其他情況比較棘手,但也有可能發生。你可能需要先運用 Split Temporary Variable 或Separate Query from Modifier 使情況變得簡單一些,然后再替換臨時變量。如果你想替換的臨時變量是用來收集結果的(例如循環中的累加值),你就需要將某些程序邏輯(例如循環)拷貝到查詢式(query method)去。
**作法(Mechanics)**
首先是簡單情況:
- 找出只被賦值一次的臨時變量。
- 如果某個臨時變量被賦值超過一次,考慮使用Split Temporary Variable 將它分割成多個變量。
- 將該臨時變量聲明為final。
- 編譯。
- 這可確保該臨時變量的確只被賦值一次。
- 將「對該臨時變量賦值」之語句的等號右側部分提煉到一個獨立函數中。
- 首先將函數聲明為private。日后你可能會發現有更多class需要使用 它,彼時你可輕易放松對它的保護。
- 確保提煉出來的函數無任何連帶影響(副作用),也就是說該函數并不修改任何對象內容。如果它有連帶影響,就對它進行Separate Query from Modifier。
- 編譯,測試。
- 在該臨時變量身上實施Replace Temp with Query。
我們常常使用臨時變量保存循環中的累加信息。在這種情況下,整個循環都可以被提為一個獨立函數,這也使原本的函數可以少掉幾行擾人的循環碼。有時候,你可能會用單一循環累加好幾個值,就像本書p.26的例子那樣。這種情況下你應該針對每個累加值重復一遍循環,這樣就可以將所有臨時變量都替換為查詢式(query)。當然,循環應該很簡單,復制這些代碼時才不會帶來危險。
運用此手法,你可能會擔心性能問題。和其他性能問題一樣,我們現在不管它,因 為它十有八九根本不會造成任何影響。如果性能真的出了問題,你也可以在優化時期解決它。如果代碼組織良好,那么你往往能夠發現更有效的優化方案;如果你沒有進行重構,好的優化方案就可能與你失之交臂。如果性能實在太糟糕,要把臨時變量放回去也是很容易的。
**范例(Example)**
首先,我從一個簡單函數開始:
~~~
double getPrice() {
int basePrice = _quantity * _itemPrice;
double discountFactor;
if (basePrice > 1000) discountFactor = 0.95;
else discountFactor = 0.98;
return basePrice * discountFactor;
}
~~~
我希望將兩個臨時變量都替換掉。當然,每次一個。
盡管這里的代碼十分清楚,我還是先把臨時變量聲明為final,檢查他們是否的確只被賦值一次:
~~~
double getPrice() {
final int basePrice = _quantity * _itemPrice;
final double discountFactor;
if (basePrice > 1000) discountFactor = 0.95;
else discountFactor = 0.98;
return basePrice * discountFactor;
}
~~~
這樣一來,如果有任何問題,編譯器就會警告我。之所以先做這件事,因為如果臨時變量不只被賦值一次,我就不該進行該項重構。接下來我開始替換臨時變量,每次一個。首先我把賦值(assignment)動作的右側表達式提煉出來:
~~~
double getPrice() {
final int basePrice = basePrice();
final double discountFactor;
if (basePrice > 1000) discountFactor = 0.95;
else discountFactor = 0.98;
return basePrice * discountFactor;
}
private int basePrice() {
return _quantity * _itemPrice;
}
~~~
編譯并測試,然后開始使用Replace Temp with Query。首先把臨時變量basePrice的第一個引用點替換掉:
~~~
double getPrice() {
final int basePrice = basePrice();
final double discountFactor;
if (basePrice() > 1000) discountFactor = 0.95;
else discountFactor = 0.98;
return basePrice * discountFactor;
}
~~~
編譯、測試、下一個(聽起來像在指揮人們跳鄉村舞蹈一樣)。由于「下一個」已經是basePrice的最后一個引用點,所以我把basePrice臨時變量的聲明式一并摘除:
~~~
double getPrice() {
final double discountFactor;
if (basePrice() > 1000) discountFactor = 0.95;
else discountFactor = 0.98;
return basePrice() * discountFactor;
}
~~~
搞定basePrice之后,我再以類似辦法提煉出一個discountFactor():
~~~
double getPrice() {
final double discountFactor = discountFactor();
return basePrice() * discountFactor;
}
private double discountFactor() {
if (basePrice() > 1000) return 0.95;
else return 0.98;
}
~~~
你看,如果我沒有把臨時變量basePrice替換為一個查詢式,將多么難以提煉discountFactor()!
最終,getPrice()變成了這樣:
~~~
double getPrice() {
return basePrice() * discountFactor();
}
~~~
- 譯序 by 侯捷
- 譯序 by 熊節
- 序言
- 前言
- 章節一 重構,第一個案例
- 起點
- 重構的第一步
- 分解并重組statement()
- 運用多態(Polymorphism)取代與價格相關的條件邏輯
- 結語
- 章節二 重構原則
- 何謂重構
- 為何重構
- 「重構」助你找到臭蟲(bugs)
- 何時重構
- 怎么對經理說?
- 重構的難題
- 重構與設計
- 重構與性能(Performance)
- 重構起源何處?
- 章節三 代碼的壞味道
- Duplicated Code(重復的代碼)
- Long Method(過長函數)
- Large Class(過大類)
- Long Parameter List(過長參數列)
- Divergent Change(發散式變化)
- Shotgun Surgery(散彈式修改)
- Feature Envy(依戀情結)
- Data Clumps(數據泥團)
- Primitive Obsession(基本型別偏執)
- Switch Statements(switch驚悚現身)
- Parallel Inheritance Hierarchies(平行繼承體系)
- Lazy Class(冗贅類)
- Speculative Generality(夸夸其談未來性)
- Temporary Field(令人迷惑的暫時值域)
- Message Chains(過度耦合的消息鏈)
- Middle Man(中間轉手人)
- Inappropriate Intimacy(狎昵關系)
- Alternative Classes with Different Interfaces(異曲同工的類)
- Incomplete Library Class(不完美的程序庫類)
- Data Class(純稚的數據類)
- Refused Bequest(被拒絕的遺贈)
- Comments(過多的注釋)
- 章節四 構筑測試體系
- 自我測試代碼的價值
- JUnit測試框架
- 添加更多測試
- 章節五 重構名錄
- 重構的記錄格式
- 尋找引用點
- 這些重構準則有多成熟
- 章節六 重新組織你的函數
- Extract Method(提煉函數)
- Inline Method(將函數內聯化)
- Inline Temp(將臨時變量內聯化)
- Replace Temp with Query(以查詢取代臨時變量)
- Introduce Explaining Variable(引入解釋性變量)
- Split Temporary Variable(剖解臨時變量)
- Remove Assignments to Parameters(移除對參數的賦值動作)
- Replace Method with Method Object(以函數對象取代函數)
- Substitute Algorithm(替換你的算法)
- 章節七 在對象之間搬移特性
- Move Method(搬移函數)
- Move Field(搬移值域)
- Extract Class(提煉類)
- Inline Class(將類內聯化)
- Hide Delegate(隱藏「委托關系」)
- Remove Middle Man(移除中間人)
- Introduce Foreign Method(引入外加函數)
- Introduce Local Extension(引入本地擴展)
- 章節八 重新組織數據
- Self Encapsulate Field(自封裝值域)
- Replace Data Value with Object(以對象取代數據值)
- Change Value to Reference(將實值對象改為引用對象)
- Replace Array with Object(以對象取代數組)
- Replace Array with Object(以對象取代數組)
- Duplicate Observed Data(復制「被監視數據」)
- Change Unidirectional Association to Bidirectional(將單向關聯改為雙向)
- Change Bidirectional Association to Unidirectional(將雙向關聯改為單向)
- Replace Magic Number with Symbolic Constant(以符號常量/字面常量取代魔法數)
- Encapsulate Field(封裝值域)
- Encapsulate Collection(封裝群集)
- Replace Record with Data Class(以數據類取代記錄)
- Replace Type Code with Class(以類取代型別碼)
- Replace Type Code with Subclasses(以子類取代型別碼)
- Replace Type Code with State/Strategy(以State/strategy 取代型別碼)
- Replace Subclass with Fields(以值域取代子類)
- 章節九 簡化條件表達式
- Decompose Conditional(分解條件式)
- Consolidate Conditional Expression(合并條件式)
- Consolidate Duplicate Conditional Fragments(合并重復的條件片段)
- Remove Control Flag(移除控制標記)
- Replace Nested Conditional with Guard Clauses(以衛語句取代嵌套條件式)
- Replace Conditional with Polymorphism(以多態取代條件式)
- Introduce Null Object(引入Null 對象)
- Introduce Assertion(引入斷言)
- 章節十一 處理概括關系
- Pull Up Field(值域上移)
- Pull Up Method(函數上移)
- Pull Up Constructor Body(構造函數本體上移)
- Push Down Method(函數下移)
- Push Down Field(值域下移)
- Extract Subclass(提煉子類)
- Extract Superclass(提煉超類)
- Extract Interface(提煉接口)
- Collapse Hierarchy(折疊繼承關系)
- Form Template Method(塑造模板函數)
- Replace Inheritance with Delegation(以委托取代繼承)
- Replace Delegation with Inheritance(以繼承取代委托)
- 章節十二 大型重構
- 這場游戲的本質
- Tease Apart Inheritance(梳理并分解繼承體系)
- Convert Procedural Design to Objects(將過程化設計轉化為對象設計)
- Separate Domain from Presentation(將領域和表述/顯示分離)
- Extract Hierarchy(提煉繼承體系)
- 章節十三 重構,復用與現實
- 現實的檢驗
- 為什么開發者不愿意重構他們的程序?
- 現實的檢驗(再論)
- 重構的資源和參考資料
- 從重構聯想到軟件復用和技術傳播
- 結語
- 參考文獻
- 章節十四 重構工具
- 使用工具進行重構
- 重構工具的技術標準(Technical Criteria )
- 重構工具的實用標準(Practical Criteria )
- 小結
- 章節十五 集成
- 參考書目