生活随笔
收集整理的這篇文章主要介紹了
重构-改善既有代码的设计:重新组织函数的九种方法(四)
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
??函數過長或者邏輯太混亂,重新組織和整理函數的代碼,使之更合理進行封裝。
1.?Extract Method 提煉函數
提煉函數:(由復雜的函數提煉出獨立的函數或者說大函數分解成由小函數組成)
你有一段代碼可以被組織在一起并獨立出來。
將這段代碼放進一個獨立函數,并讓函數名稱解釋該函數的用途
。
void?printOwing()?{??? ?????? ????System.out.println(“*********”);?? ????System.out.println(“Banner”);?? ????System.out.println(“*********”);?? ?????? ????System.out.println?("name:?"?+?_name);??? ????????System.out.println?("amount?"?+?getOutstanding());??? }???
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
void?printOwing()??{?? ????printBanner();??? ????printDetails(getOutstanding());??? }??? ?? Void?printBanner()??{?? ?????? ????System.out.println(“*********”);?? ????System.out.println(“Banner”);?? ????System.out.println(“*********”);?? }?? <strong>void?printDetails?(double?outstanding)???{??? ????System.out.println?("name:?"?+?_name);??? ????System.out.println?("amount?"?+?outstanding);??? }?</strong>??
過長的函數或者一段需要注釋才能讓人理解用途的代碼,就應該將這段代碼放進一個獨立函數中。簡短而命名良好的函數的好處:
1)如果每個函數的粒度都很小,那么函數被復用的機會就更大;
2)這會使高層函數讀起來就想一系列注釋;
3)如果函數都是細粒度,那么函數的覆寫也會更容易些。
一個函數多長才算合適?長度不是問題,關鍵在于函數名稱和函數本體之間的語義距離。如果提煉可以強化代碼的清晰度,那就去做,就算函數名稱必提煉出來的代碼還長也無所謂。
2.?Inline Method 內聯函數
內聯函數:(直接使用函數體代替函數調用 )?一個函數調用的本體與名稱同樣清楚易懂。在函數調用點插入函數體,然后移除該函數
int?getRating()?{?? ????return?moreThanfiveLateDeliverise()???2?:?1;?? }?? bool?moreThanfiveLateDeliverise()?{?? ????return?_numberOfLateLiveries?>?5;?? }??
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
int?getRating(){?? ?????return?_numberOfLateLiveries?>?5???2?:?1;?? }??
? ? ?有時候你會遇到某些函數,其內部代碼和函數名稱同樣清晰易讀。也可能你重構了該函數,使得其內容和其名稱變得同樣清晰。果真如此,你應該去掉這個函數,直接使用其中的代碼。間接性可能帶來幫助,但非必要的間接性總是讓人不舒服。
? ? ?另一種需要使用Inline Method (內聯函數)的情況是:你手上有一群不甚合理的函數。你可以將它們都內聯到一個大型函數中,再從中提煉出合理的小函數。實施Replace Method with Method Object (以函數對象取代函數)之前這么做,往往可以獲得不錯的效果。你可以把所要的函數的所有調用對象的函數內容都內聯到函數對象中。比起既要移動一個函數,又要移動它所調用的其他所有函數,將整個大型函數作為整體來移動比較簡單。
? ? 如果別人使用了太多間接層,使得系統中所有函數都似乎只是對另一個函數的簡單委托,造成在這些委托動作之間暈頭轉向,那么就使用 Inline Method (內聯函數)。
當然,間接層有其價值,但不是所有間接層都有價值。試著使用內聯手法,可以找出那些有用的間接層,同時將那些無用的間接層去除。
3.Inline Temp 內聯臨時變量
內聯臨時變量:(表達式代替臨時變量)你有一個臨時變量,只被一個簡單表達式賦值一次,而它妨礙了其他重構手法。將所有對該變量的引用動作,替換為對它賦值的那個表達式自身? ? ?
double?basePrice?=?anOrder.BasePrice();?? return?basePrice(>1000);?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ? ?
return?(anOrder.BasePrice()?>1000);??
? ? ? ? Inline Temp(內聯臨時變量)多半是作為Replace Temp with Query(以查詢取代臨時變量)的一部分使用的,所以真正的動機出現在后者那里。唯一單獨使用Inline Temp(內聯臨時變量)情況是:你發現某個臨時變量被賦予某個函數調用的返回值。
? ? ? ?一般來說,這樣的臨時變量不會有任何危害,可以放心把它留在那。但如果這個臨時變量妨礙了其他的重構手法,例如 Extract Method (提煉函數),就應該將它內聯化。
4.Replace Temp with 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;??? }???
? ? 臨時變量的問題在于:它們是暫時的,而且只能在所屬函數內使用。由于臨時變量只是在所屬函數內可見,所以它們會驅使你寫出更長的函數,因為只有這樣你才能訪問到需要的臨時變量。如果把臨時變量替換為一個查詢,那么同一個類中的所有函數都可以獲得這份信息。這將帶給你極大幫助,使你能夠為這個類編寫更清晰地代碼。
? ??
以查詢代替臨時變量
往往是你運用Extract Method(
提煉函數)
之前必不可少的一個步驟。局部變量會使代碼難以被提煉,所以你應該盡可能把它們替換為查詢式。
? ? 這個重構手法較為簡單的情況是:臨時變量只被賦值一次,或者賦值給臨時變量的表達式不受其他條件影響。其他情況比較棘手,但也可能發生。你可能需要先運用Split Temporary Variable(分解臨時變量)或Separate Query form Modifier(將查詢函數和修改函數分離) 使情況變得簡單一些,然后再替換臨時變量。如果你想替換的臨時變量是用來收集結果的)例如循環中的累加值),就需要將某些程序邏輯(例如循環)復制到查詢函數去。
5.Introduce Explaining Variable 引入解釋性變量
引入解釋性變量:(復雜表達式分解為臨時解釋性變量)你有一個復雜的表達式。將該復雜表達式(或其中一部分)的結果放進一個臨時變量,以此變量名稱來解釋表達式用途。
if?(Platform.ToUpperCass().indexOf("MAC")?>?-1?&&?(Browser.ToUpperCass().indexOf("Ie")?>?-1)?&&?WasInitalized()?)?{?? ?????? ?}?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
const?bool?imMacOs?=?Platform.ToUpperCass().indexOf("MAC")?>?-1;?? const?bool?isIeBrowser?=?Browser.ToUpperCass().indexOf("Ie")?>?-1;?? const?bool?wasInitalized?=?WasInitalized();?? if?(imMacOs?&&?isIeBrowser?&&?wasInitalized)?? {?? ?????? }?? ? ? 表達式有可能非常復雜而難以閱讀。這種情況下,臨時變量可以幫助你將表達式分解為比較容易管理的形式。
? ? 在條件邏輯中,
Introduce Explaining Variable(引入解釋性變量)是一個很常見的手法,但是最好盡量使用?
Extract Method(提煉函數)?來解釋一段代碼的意義。畢竟臨時變量只在他所處的那個函數才有意義,局限性較大。函數則可以在對象的這個生命中都有用,并且可被其他對象使用。但有時候,當局部變量使
Extract Method(提煉函數)難以進行時,就可以使用
Introduce Explaining Variable (引入解釋性變量).
6.?Split Temporary Variable 分解臨時變量
分解臨時變量:(臨時變量不應該被賦值超過一次)你的程序有某個臨時變量被賦值超過一次,它既不是循環變量,也不被用于收集計算結果。針對每次賦值,創造一個獨立、對應的臨時變量
double?temp?=?2?+?(_height?+?_width);?? Console.WriteLine(temp);?? temp?=?_height?*?_width;?? Console.WriteLine(temp);??
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
const?double?perimeter?=?2?+?(_height?+?_width);?? Console.WriteLine(perimeter);?? const?double?area?=?_height?*?_width;?? Console.WriteLine(area);??
? ? ? ? 臨時變量有各種不同用途,其中某些用途會很自然的導致臨時變量被多次賦值。“循環變量”和“結果收集變量”就是典型的例子:循環變量會隨循環的每次運行而改變; ??
結果收集變量負責將“通過這個函數的運算”而構成的某個值收集起來。
? ? ? ? 除了這2種情況,還有很多臨時變量保存一段冗長代碼的運算結果,以便稍后使用。這種臨時變量應該只被賦值一次。如果它們被賦值超過一次,
就意味著它們在函數中承擔了一個以上的職責。如果臨時變量承擔多個責任,它就應該被替換為多個臨時變量,每個變量只承擔一個責任。同一個臨時變量承擔2件不同的事情,會令代碼閱讀者糊涂。
7.Remove Assigments to Parameters 移除對參數的賦值
移除對參數的賦值:(不要對參數賦值)代碼對一個 參數賦值。以一個臨時變量取代該參數的位置。?
int?discount?(int?inputVal,?int?quantity,?int?yearToDate){??? ????if?(inputVal?>?50)?inputVal?-=?2;??? }?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
int?discount?(int?inputVal,?int?quantity,?int?yearToDate)?{??? ?????int?result?=?inputVal;??? ?????if?(inputVal?>?50)?result?-=?2;??? }?? 如果參數是Object,容易誤賦值。采用final來防止誤用參數:
要清楚“對參數賦值”這個說法的意思。如果你把一個名為foo的對象作為參數傳給某個函數,那么“對參數賦值”意味著改變foo,使它引用另外一個對象。如果你在“被傳入對象”身上進行什么操作,那沒什么問題。這里只針對“foo被改而指向另一個對象”這種情況來討論。
<span?style="font-size:12px;">void?aMethod(Object??foo)?{?? ??????foo.modifyInSomeWay();?? ??????foo?=?anotherObject;?? }</span>?? 這樣的做法降低了代碼的清晰度,而且混用了按值傳遞和按引用傳遞這2種參數傳遞方式。
在按值傳遞的情況下,對參數的任何修改,都不會對調用端造成任何影響。那些用過按引用傳遞方式的人可能會在這一點上犯糊涂。
另一個讓人糊涂的地方時函數本體內。如果你只以參數表示“被傳遞進來的東西”。那么代碼會清晰地多,因為這種用法在所有語言都表現出相同語義。
8. Replace Method with Method object 函數對象取代函數
函數對象代替函數:(大函數變成類)你有一個大型函數,其中對局部變量的使用使你無法采用 Extract Method (提煉函數)。將這個大型函數放進一個單獨對象中,如此一來局部變量就成了對象內的字段。然后你可以在同一個對象中將這個大型函數分解為多個小型函數。
class?Order...?? double?price()?{??? ????double?primaryBasePrice;?? ????double?secondaryBasePrice;??? ????double?tertiaryBasePrice;??? ?????? }??? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
? ? 或者可以采用static method
? ? ? ? 局部變量的存在會增加函數分解的難度。如果一個函數之中局部變量泛濫,那么想分解這個函數是非常困難的。Replace Temp with Query (以查詢取代臨時變量)可以幫助你減輕這一負擔,但有時候你會發現根本無法拆解一個需要拆解的函數。這種情況下,應該使用函數對象。
9.Substitute Algorithm 替換算法
替換算法:(函數本體替換為另一個算法)你想要把某個算法替換為另一個更清晰地算法。將函數本體替換為另一個算法。??
String?foundPerson(String[]?people){??? ????for?(int?i?=?0;?i?<?people.length;?i++)??{??? ????????if?(people[i].equals?("Don")){?? ?????????????return?"Don";??? ?????????}?? ?????????if?(people[i].equals?("John"))?{?? ???????????return?"John";??? ?????????}??? ?????????if?(people[i].equals?("Kent")){??? ????????????????return?"Kent";??? ?????????}??? ?????}?? ?????return?"";??? }??? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
String?foundPerson(String[]?people){??? ????List?candidates??? ?????????????=?Arrays.asList(new?String[]?{"Don",???"John",?"Kent"});??? ????for?(int?i=0;?i<people.length;?i++)??? ????????if?(candidates.contains(people[i]))??? ?????????????return?people[i];?return?"";??? }??? ? ? ? 解決問題有好幾種方法。算法也是如此。如果你發現做一件事可以有更清晰地方式,就應該以較清晰地方式取代復雜的方式。“重構”可以把一些復雜東西分解為較簡單的小塊,但有時你就必須刪除整個算法,代之以簡單的算法。隨著對問題有了更多理解,你往往會發現,在原先的做法之外,有更簡單的解決方案,此時就需要改變原來的算法。如果你開始使用程序庫,而其中提供的某些功能/特性與你自己的代碼重復,那么你也需要改變原先的算法。
? ? ? 有時候你會想要修改原先的算法,讓它去做一件與原先略有差異的事。這時候你也可以先把原先的算法替換為一個較易修改的算法,這樣后續的修改會輕松許多。使用這項重構之前,請先確定自己盡可能分解了原先函數。替換一個巨大而復雜的算法是很困難的。只有先將它分解為較簡單的小型函數,然后你才能很有把握的進行算法替換工作。
總結
以上是生活随笔為你收集整理的重构-改善既有代码的设计:重新组织函数的九种方法(四)的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。