#
作法(Mechanics)
首先是簡(jiǎn)單情況:
- 找出只被賦值一次的臨時(shí)變量==>如果某個(gè)臨時(shí)變量被賦值超過一次,考慮使用Split Temporay Variable(128)將它分割成多個(gè)變量.
- 將該臨時(shí)變量聲明為final.
- 編譯.==>這可確保該臨時(shí)變量的確只被賦值一次.
- 將[對(duì)臨時(shí)變量賦值]之語句的等號(hào)右側(cè)部分提煉到一個(gè)獨(dú)立函數(shù)中.
- ==>首先將函數(shù)聲明為private.日后你可能會(huì)發(fā)現(xiàn)有更多class需要使用它,彼時(shí)你可輕易放松對(duì)它的保護(hù).
- ==>確保提煉出來的函數(shù)無任何連帶影響(副作用),也就是說該函數(shù)并不修改任何對(duì)象內(nèi)容.如果它有連帶影響,就對(duì)它進(jìn)行Separate Query from Modifier(279).
- 編譯,測(cè)試.
- 在該臨時(shí)變量身上實(shí)施Inline Temp(119).
我們常常使用臨時(shí)變量保存循環(huán)中的累加信息.在這種情況下,整個(gè)循環(huán)都可以被提煉為一個(gè)獨(dú)立函數(shù),這也使原本的函數(shù)可以少掉幾行擾人的循環(huán)代碼.有時(shí)候,你可能會(huì)用單一循環(huán)累加好幾個(gè)值.這種情況下你應(yīng)該針對(duì)每個(gè)累加值重復(fù)一遍循環(huán),這樣就可以將所有臨時(shí)變量都替換為查詢式(query).當(dāng)然,循環(huán)應(yīng)該很簡(jiǎn)單,復(fù)制這些代碼時(shí)才不會(huì)帶來危險(xiǎn).
運(yùn)用此手法,呢可能會(huì)擔(dān)心性能問題.和其他性能問題一樣,我們現(xiàn)在不管它,因?yàn)樗邪司鸥静粫?huì)造成任何影響.如果性能真的出了問題,你也可以在優(yōu)化時(shí)期解決它.如果代碼組織良好,那么你往往能夠發(fā)現(xiàn)更有效的優(yōu)化法案;如果你沒有進(jìn)行重構(gòu),好的優(yōu)化法案就可能與你失之交臂.如果性能實(shí)在太糟糕,要把臨時(shí)變量放回去也是很容易的.
動(dòng)機(jī)(Motivation) 臨時(shí)變量的問題在于:它們是暫時(shí)的,而且只能在所屬函數(shù)內(nèi)使用.由于臨時(shí)變量只有在所屬函數(shù)內(nèi)才可見,所以它們會(huì)驅(qū)使你寫出更長(zhǎng)的函數(shù),因?yàn)橹挥羞@樣你才能訪問到想要訪問的臨時(shí)變量.如果把臨時(shí)變量替換為一個(gè)查詢式(query method),那么同一個(gè)class中的所有函數(shù)都將可以獲得這份信息.這將帶給你極大幫助,使你能夠?yàn)檫@個(gè)class編寫更清晰的代碼.
Replace Temp with Query(120)往往是你運(yùn)用Extract Method(110)之前必不可少的一個(gè)步驟.局部變量會(huì)使代碼難以被提煉,所以你應(yīng)該盡可能把它們替換為查詢式.
這個(gè)重構(gòu)手法較為直率的情況就是:臨時(shí)變量只被賦值一次,或者賦值給臨時(shí)變量的表達(dá)式不受其他條件影響.其他情況比較棘手,但也有可能發(fā)生.你可能需要先運(yùn)用Split Temporary Variable(128)或Separate Query from Modifier(279)使情況變得簡(jiǎn)單一些,然后再替換臨時(shí)變量.如果你想替換的臨時(shí)變量是用來收集結(jié)果的(例如循環(huán)中的累加值),你就需要將某些程序邏輯(例如循環(huán))拷貝到查詢式(query method)去.
你的程序以一個(gè)臨時(shí)變量(temp)保存某一表達(dá)式的運(yùn)算結(jié)果.
將這個(gè)表達(dá)式提煉到一個(gè)獨(dú)立函數(shù)中.將這個(gè)臨時(shí)變量的所有[被引用點(diǎn)]替換為[對(duì)新函數(shù)的調(diào)用].新函數(shù)可被其他函數(shù)使用.
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 base() * 0.98; ... double basePrice() { return _quantity * _itemPrice; }
-
作法(Mechanics)
-
如果這個(gè)臨時(shí)變量并未被聲明為final,那就將它聲明為final,然后編譯.==>這可以檢查該臨時(shí)變量是否真的只被賦值一次.
-
找到該臨時(shí)變量的所有引用點(diǎn),將它們替換為[為臨時(shí)變量賦值]之語句中的等號(hào)右側(cè)表達(dá)式.
-
每次修改后,編譯并測(cè)試.
-
修改完所有引用電之后,刪除該臨時(shí)變量的聲明式和賦值語句.
-
編譯,測(cè)試.
動(dòng)機(jī)(Motivation) Inline Temp(119)多半是作為Replace Temp with Query(120)的一部分來使用,所以真正的動(dòng)機(jī)出現(xiàn)在后者那兒.唯一單獨(dú)使用Inline Temp(119)的情況是:你發(fā)現(xiàn)某個(gè)臨時(shí)變量被賦予某個(gè)函數(shù)調(diào)用的返回值.一般來說,這樣的臨時(shí)變量不會(huì)有任何危害,你可以放心地把它留在那兒.但如果這個(gè)臨時(shí)變量妨礙了其他的重構(gòu)手法--例如Extract Method(110),你就應(yīng)該就它inline化.
你有一個(gè)臨時(shí)變量,只被一個(gè)簡(jiǎn)單表達(dá)式賦值一次,而它妨礙了其他重構(gòu)方法.
將所有對(duì)該變量的引用動(dòng)作,替換為對(duì)它賦值得那個(gè)表達(dá)式自身.
double basePrice = anOrder.basePrice(); return (basePrice > 1000); | | | | \ / return (anOrder.basePrice() > 1000);
-
作法(Mechanics)
-
檢查函數(shù),確定它不具多態(tài)性(is not polymorphic).==>如果subclass繼承了這個(gè)函數(shù),就不要將此函數(shù)inline化,因?yàn)閟ubclass無法覆寫(override)一個(gè)根本不存在的函數(shù).
-
找出這個(gè)函數(shù)的所有被調(diào)用點(diǎn).
-
將這個(gè)函數(shù)的所有被調(diào)用點(diǎn)都替換為函數(shù)本體(代碼).
-
編譯,測(cè)試.
-
刪除該函數(shù)的定義.
被我這樣一寫,Inline Method(117)似乎很簡(jiǎn)單.但情況往往并非如此.對(duì)于遞歸調(diào)用,多返回點(diǎn),inline至另一個(gè)對(duì)象中而該對(duì)象并無提供訪問函數(shù)(accessors)......,每一種情況我都可以寫上好幾頁.我之所以不寫這些特殊情況,原因很簡(jiǎn)單:如果你遇到了這樣的復(fù)雜情況,那么就不應(yīng)該使用這個(gè)重構(gòu)手法.
動(dòng)機(jī)(Motivation) 有時(shí)候你會(huì)遇到某些函數(shù),其內(nèi)部代碼和函數(shù)名稱同樣清晰易讀.
另一種需要使用Inline Method(117)的情況是:你手上有一群組織不甚合理的函數(shù).你可以將它們都inline到一個(gè)大型函數(shù)中,再?gòu)闹刑釤挸鼋M織合理的小型函數(shù).Kent Beck發(fā)現(xiàn),實(shí)施Replace Method with Method Object(135)之前先這么做,往往可以獲得不錯(cuò)的效果.你可以把你所要的函數(shù)(有著你要的行為)的所有調(diào)用對(duì)象的函數(shù)內(nèi)容都inline到method object(函數(shù)對(duì)象)中.比起既要移動(dòng)一個(gè)函數(shù),又要移動(dòng)它所調(diào)用的其他所有函數(shù),[將大型函數(shù)作為單一整體來移動(dòng)]會(huì)比較簡(jiǎn)單.
如果別人使用了太多間接層,使得系統(tǒng)中的所有函數(shù)都似乎只是對(duì)另一個(gè)函數(shù)的簡(jiǎn)單委托(delegation),造成我在這些委托動(dòng)作之間暈頭轉(zhuǎn)向,那么我通常都會(huì)使用Inline Method(117).當(dāng)然,間接層有其價(jià)值,但不是所有間接層都有價(jià)值.試著使用inlining,我可以找出那些有用的間接層,同時(shí)將那些無用的間接層去除.
一個(gè)函數(shù),其本體(method body)應(yīng)該與其名稱(method name)同樣清楚易懂.
在函數(shù)調(diào)用點(diǎn)插入函數(shù)本體,然后移除該函數(shù).
int getRating() { return (moreThanFiveLateDeliveries()) ? 2 : 1; } boolean moreThanFiveLateDeliveries() { return _numberOfLateDeliveries > 5; } | | | | \ / int getRating() { return (_numberOfLateDeliveries > 5) ? 2 : 1; }
范例(Examples):對(duì)局部變量再賦值(Reassigning) 如果被提煉碼對(duì)局部變量賦值,問題就變得復(fù)雜了.這里我們只討論臨時(shí)變量的問題.如果你發(fā)現(xiàn)源函數(shù)的參數(shù)被賦值,應(yīng)該馬上使用Remove Assignments to Parameters(131).
被賦值的臨時(shí)變量也分兩種情況.較簡(jiǎn)單的情況是:這個(gè)變量只在被提煉碼區(qū)段中使用.果真如此,你可以將這個(gè)臨時(shí)變量的聲明式移到被提煉碼中,然后一起提煉出去.另一種情況是:被提煉碼之外的代碼也使用了這個(gè)變量.這又分為兩種情況:如果這個(gè)變量在被提煉碼之后未再被使用,你只需直接在目標(biāo)函數(shù)中修改它就可以了;如果被提煉碼之后的代碼還使用了這個(gè)變量,你就需要讓目標(biāo)函數(shù)返回該變量改變后的指.我以下列代碼說明這幾種不同情況: void printOwing() { Enumeration e = _orders.elements(); double outstanding = 0.0;
printBanner();
// calculate outstanding while(e.hasMoreElements()) { Order each = (Order) e.nextElement(); outstanding += each.getAmount(); }
printDetails(outstanding); }
現(xiàn)在我把[計(jì)算]代碼提煉出來:
void printOwing() { printBanner(); double outstanding = getOutstanding(); printDetails(outstanding); }
double getOutstanding() { Enumeration e = _orders.elements(); double outstanding = 0.0; while(e.hasMoreElements()) { Order each = (Order) e.nextElement(); outstanding += each.getAmount(); } return outstanding; }
Enumeration變量e只在被提煉碼中用到,所以我可以將它整個(gè)搬到新函數(shù)中.double變量outstanding在被提煉碼內(nèi)外都被用到,所以我必須讓提煉出來的新函數(shù)返回它.編譯測(cè)試完成后,我就把傳值改名,遵循我的一貫命名原則:
double getOutstanding() { Enumeration e = _orders.elements(); double result = 0.0; while(e.hasMoreElements()) { Order each = (Order) e.nextElement(); result += each.getAmount(); } return result; }
本例中的outstanding變量只是很單純地被初始化為一個(gè)明確初值,所以我可以只在新函數(shù)中對(duì)它初始化.如果代碼還對(duì)這個(gè)變量做了其他處理,我就必須將它的值作為參數(shù)傳給目標(biāo)函數(shù).對(duì)于這種變化,最初代碼可能是這樣:
void printOwing(double previousAmount) { Enumeration e = _orders.elements(); double outstanding = previousAmount * 1.2;
printBanner();
// calculate outstanding while(e.hasMoreElements()) { Order each = (Order) e.nextElement(); outstanding += each.getAmount(); }
printDetails(outstanding); }
提煉后的代碼可能是這樣:
void printOwing(double previousAmount) { double outstanding = previousAmount * 1.2;
printBanner(); double outstanding = getOutstanding(outstanding); printDetails(outstanding); } double getOutstanding(double initialValue) { double result = initialValue; Enumeration e = _orders.elements(); while(e.hasMoreElements()) { Order each = (Order) e.nextElement(); result += each.getAmount(); } return result; } 編譯并測(cè)試后,我再將變量outstanding的初始化過程整理一下: void printOwing(double previousAmount) { printBanner(); double outstanding = getOutstanding(previousAmount * 1.2); printDetails(outstanding); }
這時(shí)候,你可能會(huì)問:[如果需要返回的變量不止一個(gè),又該怎么辦?]
你有數(shù)種選擇.最好的選擇通常是:挑選另一塊代碼來提煉.我比較喜歡讓每個(gè)函數(shù)都只返回一個(gè)值,所以我會(huì)安排多個(gè)函數(shù),用以返回多個(gè)值.如果你使用的語言支持[輸出式參數(shù)](output parameters),你可以使用它們帶回多個(gè)回傳值.但我還是盡可能選擇單一返回值.
臨時(shí)變量往往為數(shù)眾多,甚至?xí)固釤捁ぷ髋e步維艱.這種情況下,我會(huì)嘗試先運(yùn)用Replace Temp with Query(120)減少臨時(shí)變量.如果即使這么做了提煉依舊困難重重,我就會(huì)動(dòng)用Replace Method with Method Object(135),這個(gè)重構(gòu)手法不在乎代碼中有多少臨時(shí)變量,也不在乎你如何使用它們.
|