JavaScript代码重构系列-重新组织你的函数

原文地址:http://www.monring.com/front_end/javascript-refactor-composing-methods.html


JavaScript代码重构系列中,最重要的要算這節了: 重新组织你的函数

  1. 提炼函数

  2. 将函数内联化

  3. 用查询取代临时变量

  4. 以临时变量取代高消耗的查询

  5. 将临时变量内联化

  6. 引入解释性变量

  7. 剖解临时变量

  8. 移除对参数的赋值动作

  9. 以函数对象取代函数

  10. 替换你的算法



提炼函数

信號:你有一段代码可以被组织在一起并独立出来。

操作:将这段代码放进一个独立函数中,并让函数名称解释该函数的用途。

當你有一個很長的函數,這也是一個非常直接的重構信號,但真正能讓你用【提煉函數】方法重構代碼時候,必須發現有一段代碼可以被組織在一起,也就是說這段代碼能夠看作一個獨立的工作模塊。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/*
  * 原函數
  */
composingMethodsDemo.printOwing =  function  (order) {
     //print header
     document.writeln( '===================' );
     document.writeln( '== print amount ===' );
     document.writeln( '===================' );
     var  _order = order,
         outstanding = 0;
     for  ( var  i = 0, len = _order.records.length; i < len; i++) {
         outstanding = outstanding + _order.records[i].expenditure;
     }
     //print detail
     document.writeln( 'name: '  + _order.name);
     document.writeln( 'amount: '  + outstanding);
};
/*
  * 重構後函數
  */
composingMethodsDemo.newPrintOwing =  function  (order) {
     var  outstanding =  this .getOutstanding(order);
     this .extPrintHeader();
     this .extPrintDetail(order.name, outstanding);
};
// 无局部变量,直接提出
composingMethodsDemo.extPrintHeader =  function  () {
     document.writeln( '===================' );
     document.writeln( '== print amount ===' );
     document.writeln( '===================' );
};
// 有局部变量, 以參數傳遞形式輸入
composingMethodsDemo.extPrintDetail =  function  (name, outstanding) {
     document.writeln( 'name: '  + name);
     document.writeln( 'amount: '  + outstanding);
};
// 对局部变量再赋值
composingMethodsDemo.getOutstanding =  function  (order) {
     var  outstanding = 0;
     for  ( var  i = 0, len = order.records.length; i < len; i++) {
         outstanding = outstanding + order.records[i].expenditure;
     }
     return  outstanding;
};

将函数内联化

信號:一个函数,它本体应该与其名称同样清楚易懂。

操作:在函数调用点插入函数本体,然后移除该函数。

這條重構方法跟【提煉函數】是相對立的,如果你發現你【提煉函數】的函數它的內容跟它名字一樣清晰易懂,那麼還是把它內聯回去,讓它看上去更直接。

也有可能【提煉函數】重構完後,長函數(主函數)中添加了一些代碼,這時候被提取出來的函數寫到長函數中更直接,那我們也需要先內聯回去,然後再看它和其他代碼一起組織成新函數是否能再利用【提煉函數】。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/*
  * 原函數
  */
composingMethodsDemo.getRating =  function  (numberOfLateDeliveries) {
     return  ( this .moreThanFiveLateDeliveries(numberOfLateDeliveries)) ? 2 : 1;
};
composingMethodsDemo.moreThanFiveLateDeliveries =  function  (numberOfLateDeliveries) {
     return  numberOfLateDeliveries > 5;
};
/*
  * 重構後
  */
composingMethodsDemo.getRating =  function  (numberOfLateDeliveries) {
     return  (numberOfLateDeliveries > 5) ? 2 : 1;
};

用查询取代临时变量

信號:你的程序以一个临时变量保存某一表达式的运算结果。

操作:将这个表达式提炼到一个独立函数中。将这个临时变量的所有被引用点替换为对新函数的调用。新函数可被其他函数使用。

臨時變量一般在函數內部實用,這樣就促使你不得不讓你的函數變得更長,這樣才能使你的代碼訪問到你的臨時變量。這時候,如果將臨時變量換成一個查詢式,那麼代碼就更清晰,更簡潔了,但這個需要將高消耗的查詢除外。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 用查询取代临时变量
var  composingMethodsDemo = {
     _quantity: 30,
     _itemPrice: 12
}
/*
  * 原函數
  */
composingMethodsDemo.getTotalPrice =  function  () {
     var  basePrice =  this ._quantity *  this ._itemPrice;
     if  (basePrice > 1000) {
         return  basePrice * 0.95;
     else  {
         return  basePrice * 0.98;
     }
};
/*
  * 重構後
  */
composingMethodsDemo.getTotalPrice =  function  () {
     if  ( this .getBasePrice() > 1000) {
         return  this .getBasePrice() * 0.95;
     else  {
         return  this .getBasePrice() * 0.98;
     }
};
composingMethodsDemo.getBasePrice =  function  () {
     return  this ._quantity *  this ._itemPrice;
};

用临时变量取代高消耗的查询

信號:多次調用的查詢式是一個高消耗的函數

操作:用臨時變量儲存該查詢,將該查詢式調用處用臨時變量代替

該方法與【用查询取代临时变量】是對立的,但出發點不同,本方法出發點最重要的為了提升代碼性能,一些高消耗的方法如果在函數體中多次調用,我們需要用臨時變量緩存起來。最常見的是DOM查詢式,高循環運算的函數查詢式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 用临时变量取代高消耗的查询
composingMethodsDemo.getTotalPrice =  function  () {
     if  ( this .getBasePrice() > 1000) {
         return  this .getBasePrice() * 0.95;
     else  {
         return  this .getBasePrice() * 0.98;
     }
};
composingMethodsDemo.getBasePrice =  function  () {
     return  parseFloat(document.getElementById( 'quantity' ).value) * parseFloat(document.getElementById( 'itemPrice' ).value);
};
/*
  * 重構後
  */
composingMethodsDemo.getTotalPrice =  function  () {
     var  basePrice =  this .getBasePrice();
     if  (basePrice > 1000) {
         return  basePrice * 0.95;
     else  {
         return  basePrice * 0.98;
     }
};
composingMethodsDemo.getBasePrice =  function  () {
     return  parseFloat(document.getElementById( 'quantity' ).value) * parseFloat(document.getElementById( 'itemPrice' ).value);
};

将临时变量内联化

信號:你有一个临时变量,只被一个简单表达式赋值一次,而它妨碍了其他重构手法。

操作:将所有对该变量的引用动作,替换为对它赋值的那个表达式本身。

本方法多半是作为【用查询取代临时变量】的一部分来使用,所以真正的动机出现在后者那儿。惟一单独使用本方法的情况是:你发现某个临时变量被赋予某个函数调用的返回值。一般来说,这样的临时变量不会有任何危害,你可以放心地把它留在那儿。但如果这个临时变量妨碍了其他的重构方法(例如【提取函數】),你就应该将它內聯化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 将临时变量内联化
var  composingMethodsDemo = {
     _quantity: 30,
     _itemPrice: 12
};
/*
  * 原函數
  */
composingMethodsDemo.isOverMaxPrice =  function  () {
     var  basePrice =  this .getBasePrice();
     return  basePrice > 1000;
};
composingMethodsDemo.getBasePrice =  function  () {
     return  this ._quantity *  this ._itemPrice;
};
/*
  * 重構後
  */
composingMethodsDemo.isOverMaxPrice =  function  () {
     return  this .getBasePrice() > 1000;
};
composingMethodsDemo.getBasePrice =  function  () {
     return  this ._quantity *  this ._itemPrice;
};

引入解释性变量

信號:你有一个复杂的表达式。

操作:将该表达式(或其中一部分)的结果放进一个临时变量,以此变量名称来解释表达式用途。

【引入解釋性變量】能使方法體看上去更加易懂。特別是當表達式特別複雜的情況下,更能體現該方法的優勢。該方法非常有用,但很多時候我們都實用【提取函數】的方法來重構,畢竟臨時變量只對當前函數體有效,所以只有當局部變量令【提取函數】難以進行,我們才實用【引入解釋性變量】方法來處理重構。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 引入解释性变量
/*
  * 原函數
  */
composingMethodsDemo.getMacIEVersion =  function  (platform, browser) {
     if  ((platform.toUpperCase().indexOf( "MAC" ) > -1) &&
         (browser.toUpperCase().indexOf( "IE" ) > -1)) {
         return  browser.version;
     }
     return  null ;
};
/*
  * 重構後
  */
composingMethodsDemo.getMacIEVersion =  function  (platform, browser) {
     var  isMac = platform.toUpperCase().indexOf( "MAC" ) > -1,
         isIE = browser.toUpperCase().indexOf( "IE" ) > -1;
     if  (isMac && isIE) {
         return  browser.version;
     }
     return  null ;
};

剖解临时变量

信號:你的程序有某个临时变量被赋值超过一次,它既不是循环变量,也不是一个集用临时变量(collecting temporary variable)。

操作:针对每次赋值,创造一个独立的、对应的临时变量。

除了循環變量和集用臨時變量這兩種臨時變量被多次賦值的情況,其他被多次賦值的臨時變量都在敲醒我們重構的警鐘。每個臨時變量應該只承擔一個單獨的責任,例如【用临时变量取代高消耗的查询】中生成的臨時變量,如果賦值多次,就應該重構以多個臨時代替。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 剖解临时变量
composingMethodsDemo.getActivedUsers =  function  (users) {
     var  names = [],  // 集用临时变量
         count = users.length;
     for  ( var  i = 0; i < count; i++) {  // i就是循环变量
         if  (users[i].actived) {
             names.push(users[i].name);
         }
     }
     return  names;
};
/*
  * 原函數
  */
composingMethodsDemo.printRectangleInfo =  function  (height, width) {
     var  temp = 2 * (height + width);
     document.writeln(temp);
     temp = height * width;
     document.writeln(temp);
};
/*
  * 重構後
  */
composingMethodsDemo.printRectangleInfo =  function  (height, width) {
     var  perimeter = 2 * (height + width),
         area = height * width;
     document.writeln(perimeter);
     document.writeln(area);
};

移除对参数的赋值动作

信號:你的代码对一个参数进行赋值动作。

操作:以一个临时变量取代该参数的位置,以函數返回值在原函數改變對象。

在JavaScript中非常特別的一個地方,但一個對象作為參數傳遞時,傳遞的往往是一個原始的引用,甚至都不是副本。這樣出現的問題就是,參數被修改後會引起原函數中的對象會相應被修改,引起很多混雜不清晰的現象,甚至會是錯誤。所以如果參數是一個值類型,可以以一个临时变量取代该参数的位置。如果參數為一個引用類型,這時就需要避免這種情況出現。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
// 移除对参数的赋值动作
/*
  * 原函數(值傳遞)
  */
composingMethodsDemo.printAreaSize =  function  (height, width, padding) {
     height = height + 2 * padding;
     width = width + 2 * padding;
     document.writeln(height * width);
};
/*
  * 重構後(值傳遞)
  */
composingMethodsDemo.printAreaSize =  function  (height, width, padding) {
     var  outHeight = height + 2 * padding;
     outWidth = width + 2 * padding;
     document.writeln(outHeight * outWidth);
};
/*
  * 原函數(引用傳遞)
  */
composingMethodsDemo.printRectangleInfo =  function  (height, width) {
     var  rectang = {
         height: 12,
         width: 15,
         padding: 5
     };
     this .printArea(rectang);         // 這裡正確
     this .printPerimeter(rectang);    // 由於rectang引用在printArea中被修改,這裏的輸出結構就錯了
};
composingMethodsDemo.printArea =  function  (rectang) {
     //這裡需要避免修改,如需更改參數對象,請在原函數進行修改
     rectang.height = rectang.height + 2 * rectang.padding;
     rectang.width = rectang.width + 2 * rectang.padding;
     document.writeln(rectang.height * rectang.width);
};
composingMethodsDemo.printPerimeter =  function  (rectang) {
     rectang.height = rectang.height + 2 * rectang.padding;
     rectang.width = rectang.width + 2 * rectang.padding;
     document.writeln(2 * (rectang.height + rectang.width));
};
/*
  * 重構後(引用傳遞)
  */
composingMethodsDemo.printRectangleInfo =  function  (height, width) {
     var  rectang = {
         height: 12,
         width: 15,
         padding: 5
     };
     this .printArea(rectang);         // 這裡正確, 但rectang被更改 
     this .printPerimeter(rectang);    // 輸出錯誤,rectang再次被更改
};
composingMethodsDemo.printArea =  function  (rectang) {
     var  height = rectang.height + 2 * rectang.padding,
     width = rectang.width + 2 * rectang.padding;
     document.writeln(height * width);
};
composingMethodsDemo.printPerimeter =  function  (rectang) {
     var  height = rectang.height + 2 * rectang.padding,
     width = rectang.width + 2 * rectang.padding;
     document.writeln(2 * (height + width));
};

以函数对象取代函数

信號:你有一个大型函数,其中对局部变量的使用,使你无法釆用【提取函數】。

操作:将这个函数放进一个单独对象中,如此一来局部变量就成了对象内的值域(field) 然后你可以在同一个对象中将这个大型函数分解为数个小型函数。 

如果一個大型函數,功能比較單一,且局部變量很多,這時候我們沒辦法採用單純的【提取函數】方法來重構,這個時候我們就可以把大型函數轉換成一個函數對象來處理,然後將函數體分解成小函數。


替换你的算法

信號:把某个算法替换为另一个更清晰的算法。

操作:将函数本体(method body)替换为另一个算法。

算法過於複雜,或則循環過於深入都是影響代碼閱讀的因素,這裡我們需要了解循环复杂度。可以用【提取函數】或替換算法來解決這類複雜問題。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值