#
作法(Mechanics)
- 在[待剖解]之臨時變量的聲明式及其第一次被賦值處,修改其名稱。
- ==》如果稍后之賦值語句是[i = i + 某表達式]形式,就意味這是個集用臨時變量,那么就不要剖解它。集用臨時變量的作用通常是累加、字符串接合、寫入stream或者向群集(collection)添加元素。
- 將新的臨時變量聲明為final。
- 以該臨時變量之第二次賦值動作為界,修改此前對該臨時變量的所有引用點,讓它們引用新的臨時變量。
- 在第二次賦值處,重新聲明原先那個臨時變量。
- 編譯,測試。
- 逐次重復上述過程。每次都在聲明處對臨時變量易名,并修改下次賦值之前的引用點。
動機(Motivation)
臨時變量有各種不同用途,其中某些用途會很自然地導致臨時變量被多次賦值。[循環變量]和[集用臨時變量]就是兩個典型例子:循環變量(loop variable)會隨循環的每次運行而改變(例如for(int i=0; i <10;i++)語句中的i);集用臨時變量(collection temporary variable)負責將[通過整個函數的運算]而構成的某個值收集起來。
除了這兩種情況,還有很多臨時變量用于保存一段冗長代碼的運算結果,以便稍后使用。這種臨時變量應該只被賦值一次。如果它們被賦值超過一次,就意味它們在
函數中承擔了一個以上的責任。如果臨時變量承擔多個責任,它就應該被替換(剖解)為多個臨時變量,每個變量只承擔一個責任。同一個臨時變量承擔兩件不同的
事情,會令代碼閱讀者糊涂。
你的程序有某個臨時變量被賦值超過一次,它既不是循環變量,也不是一個集用臨時變量(collection temporary variable)。
針對每次賦值,創造一個獨立的、對應的臨時變量。
double temp = 2 * (_height + _widgth);
System.out.println(temp);
temp = _height * _widgth;
System.out.println(temp);
| |
\ /
final double perimeter = 2 * (_height + _widgth);
System.out.println(perimeter);
final double area = _height * _widgth;
System.out.println(area);
google talk使用了Jabber協議,因此我們這些linuxfans可以很happy地使用google的服務:)
登入選項
協議:Jabber
用戶名:gmail郵箱前綴
服務器:gmail.com
資源:google talk
密碼:gmail郵箱密碼
顯示主要選項:
Jabber選項
選中“若可用則使用tls和允許在不加密流上的純文本驗證
端口:5222
連接服務器:64.233.167.125
運用Extract Method處理上述范例 面對上代碼,我通常不會以臨時變量來解釋其動作意圖,我更喜歡使用Extract Method(110).讓我們回到起點: double price() { //price is base price - quantity discount + shipping return _quantity * _itemPrice - Math.max(0, _quantity - 500) * _itemPrice * 0.05 + Math.min(_quantity * _itemPrice * 0.1, 100.0); } 這次我把底價計算提煉到一個獨立函數中: double price() { //price is base price - quantity discount + shipping return basePrice() - Math.max(0, _quantity - 500) * _itemPrice * 0.05 + Math.min(basePrice() * 0.1, 100.0); } private double basePrice() { return _quantity * _itemPrice; }
我繼續我的提煉,每次提煉出一個新函數.最后得到下列代碼: double price() { //price is base price - quantity discount + shipping return basePrice() - quantityDiscount() + shipping(); } private double quantityDiscount() { Math.max(0, _quantity - 500) * _itemPrice * 0.05; } private double shipping() { Math.min(basePrice() * 0.1, 100.0); } private double basePrice() { return _quantity * _itemPrice; }
我比較喜歡使用Extract Method(110),因為同一對象中的任何部分,都可以根據自己的需要去取用這些提煉出來的函數.一開始我會這些新函數聲明為private;如果其他對象也需要它們,我可以輕易釋放這些函數的訪問限制.我還發現,Extract Method(110)的工作量通常并不必Introduce Explaining Variable(124)來得大.
那么,應該在什么時候使用Introduce Explaining Variable(124)呢?答案是:在Extract Method(110)需要花費更大工作量時.如果我要處理的是一個擁有大量局部變量的算法,那么使用Extract Method(110)絕非易事.這種情況下我會使用Introduce Explaining Variable(124)幫助我清理代碼,然后再考慮下一步該怎么辦.搞清楚代碼邏輯之后,我總是可以運用Replace Temp with Query(120)把被我引入的那些解釋性臨時變量去掉.況且,如果我最終使用Replace Method with Method Object(135),那么被我引入的那些解釋性臨時變量也有其價值.
范例(Examples) 我們從一個簡單計算開始: double price() { //price is base price - quantity discount + shipping return _quantity * _itemPrice - Math.max(0, _quantity - 500) * _itemPrice * 0.05 + Math.min(_quantity * _itemPrice * 0.1, 100.0); } 這段代碼還算簡單,不過我可以讓它變得更容易理解.首先我發現,底價(base price)等于數量(quantity)乘以單價(item price).于是我把這一部分計算的結果放進一個臨時變量中: double price() { //price is base price - quantity discount + shipping final double basePrice = _quantity * _itemPrice; return basePrice - Math.max(0, _quantity - 500) * _itemPrice * 0.05 + Math.min(_quantity * _itemPrice * 0.1, 100.0); }
稍后也用上了[數量乘以單價]運算結果,所以我同樣將它替換為basePrice臨時變量: double price() { //price is base price - quantity discount + shipping final double basePrice = _quantity * _itemPrice; return basePrice - Math.max(0, _quantity - 500) * _itemPrice * 0.05 + Math.min(basePrice * 0.1, 100.0); }
然后,我將批發折扣(quantity discount)的計算提煉出來,將結果賦予臨時變量quantityDiscount: double price() { //price is base price - quantity discount + shipping final double basePrice = _quantity * _itemPrice; final double quantityDiscount = Math.max(0, _quantity - 500) * _itemPrice * 0.05; return basePrice - quantityDiscount + Math.min(basePrice * 0.1, 100.0); }
最后,我再把運費(shipping)計算提煉出來,將運算結果賦予臨時變量shipping.同時我還可以刪掉代碼中的注釋,因為現在代碼已經可以完美表達自己的意義了: double price() { //price is base price - quantity discount + shipping final double basePrice = _quantity * _itemPrice; final double quantityDiscount = Math.max(0, _quantity - 500) * _itemPrice * 0.05; final double shipping = Math.min(basePrice * 0.1, 100.0); return basePrice - quantityDiscount + shipping; }
作法(Mechanics)
- 聲明一個final 臨時變量,將待分解之復雜表達式中的一部分動作的運算結果賦值給它.
- 將表達式中的[運算結果]這一部分,替換為上述臨時變量.
- ==>如果被替換的這一部分在代碼中重復出現,你可以每次一個,逐一替換.
- 編譯,測試.
- 重復上述過程,處理表達式的其他部分.
動機(Motivation) 表達式有可能非常復雜而難以閱讀.這種情況下,臨時變量可以幫助你將表達式分解為比較容易管理的形式.
在條件邏輯(conditional logic)中,Introduce Explaining Variable(124)特別有價值:你可以用這項重構將每個條件子句提煉出來,以一個良好命名的臨時變量來解釋對應條件子句的意義.使用這項重構的另一種情況是,在較長算法中,可以運用臨時變量來解釋每一步運算的意義.
Introduce Explaining Variable(124)是一個很常見的重構手法,但我得承認,我并不常用它.我幾乎總是盡量使用Extract Method(110)來解釋一段代碼的意義.畢竟臨時變量只在它所處的那個函數中才有意義,局限性較大,函數則可以對象的整個生命中都有用,并且可被其他對象使用.但有時候,當局部變量使Extract Method(110)難以進行時,我就使用Introduce Explaining Variable(124).
你有一個復雜的表達式.
將該復雜表達式(或其中一部分)的結果放進一個臨時變量,以此變量名稱來解釋表達式用途.
if((platform.toUpperCase().indexOf("MAC") > -1) && (brower.toUpperCase().indexOf("IE") > -1) && wasInitialized() && resize > 0) { //do something } | | | | \ / final boolean isMacOs = platform.toUpperCase().indexOf("MAC") > -1; final boolean isIEBrowser = browser.toUpperCase().indexOf("IE") > -1; final boolean wasResized = resize > 0;
if(isMacOs && isIEBrowser && wasInitialized() && wasResized) { //do something }
范例(Examples) 首先,我從一個簡單函數開始: 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; } 編譯并測試,然后開始使用Inline Temp(119).首先把臨時變量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(); }
|