【重構筆記02】從新組織函數

前言

重構過程當中,仍是有必定標準可循的,每一個重構手法有以下五個部分:web

首先是名稱(name),建造一個重構詞彙表,名稱是很是重要的 而後是一個簡短概要,介紹重構手法適用的場景,以及他乾的事情,這樣咱們能夠快速找到所需重構方法算法

而後,介紹爲何須要這個重構,或者什麼狀況下適用這個重構作法,簡明扼要的介紹如何一步步重構函數

最後,以一個十分簡單的例子說明此重構如何運做性能

因此今天咱們進入重構的學習吧!學習

提煉函數

咱們重構是,重頭戲就是處理函數,以js而言,函數重要性更是終於「類」的概念。測試

如何恰當的包裝代碼,如何減小過長的代碼,這是咱們多數時刻須要思考的。this

可是要消除函數過長是不易的,一個函數過長說明這個函數所完成的業務很複雜,並且可能關聯性很高,要將這樣的代碼拆分,就不止是重構的事情了spa

在此提煉函數變得十分考驗一我的的水平,如何將一段代碼從原先函數提取出來,如何將一個單數調用替換爲函數本體,這些都不簡單prototype

最後改完,時常發現提煉的某些函數實際意義不大,咱們還得考慮如何回溯原來的函數code

難在何處

提煉函數不易,難在處理局部變量,臨時變量尤爲突出

處理一個函數時,咱們能夠先使用查詢取代變量的方法取代臨時變量

若是一個臨時變量屢次使用,可使用分解臨時變量的方法將它變得容易替換

可是,多數時候臨時變量確實混亂,難以替換,這時候咱們可使用以函數對象取代函數的方法,這樣的代價就會引入新類

參數帶來的問題比臨時變量少一點,前提是不在函數內爲他賦值(不對參數賦值,對js來講就是一個傳說,由於咱們隊參數賦值能夠保證程序更健壯),可是移除對象賦值,也許能帶給你不同的感覺

說了這麼多,咱們來好好審視下,咱們的一些手段吧!!!

光說無碼不行,咱們先上一個例子,咱們如今將這段代碼放到一個獨立的函數中,注意函數名須要解釋函數用途哦

 1 var log = function (msg) { console.log(msg); };
 2 
 3 var printOwing = function (amount) {
 4     printBanner();
 5     log('name:' + _name);
 6     log('amount:' + _amount);
 7 };
 8 
 9 var printOwing = function (amount) {
10     printBanner();
11     printDetails(amount)
12 };
13 
14 var printDetails = function (amount) {
15     log('name:' + _name);
16     log('amount:' + _amount)
17 };

這是比較常見的重構手法,當咱們看到一個過長的函數或者一段須要註釋才能看懂的代碼時,這段代碼可能就須要放進獨立的函數了

若是每一個函數的粒度都很小,那麼函數被複用的機會就大,這樣高層函數看上去就像被函數名註釋似的,這樣函數複寫也相對簡單

如何作?

① 創造一個新函數,根據這個函數的意圖來對它命名(以它「作神馬」來命名,而不是以它"怎麼作"命名)

PS:即便咱們要提煉的代碼很是簡單,哪怕只是一個消息或者一個函數調用,只要新函數能更好的表示代碼意圖,就能夠提煉,不然就不要動他了

② 將提煉的代碼拷貝到新建函數中

③ 檢查提煉的代碼,看看其中是否引用了「做用域限於原函數」的變量(局部變量、原函數參數)

④ 檢查是否包含「僅用於被提煉代碼段」的臨時變量,若是有,在目標函數中將之聲明爲局部變量

⑤ 檢查被提煉代碼段,看看是否有任何局部變量的值被他改變,若是一個臨時變量的值被修改了,看看是否能夠將提煉的代碼變爲一個查詢,將結果給相關變量

若是這樣很差作,或者被修改的變量不止一個,拷貝的方式可能就不適用了,這個時候可能還須要用到(分解臨時變量/以查詢替換變量)等手段了

⑥ 將被提煉代碼段中須要被讀取的局部變量,當參數傳給目標函數

⑦ 處理結束後檢查測試之,便結束!

好了,咱們再來幾個例子

無局部變量

 1 var log = function (msg) { console.log(msg); };
 2 var printOwing = function (amount) {
 3     //var productList = [];//這個數據你懂的
 4     var outstanding = 0;
 5     log('*****************');
 6     log('****Cunstomer Owes*****');
 7     log('*****************');
 8 
 9     for (var k in productList) {
10         outstanding += productList[k].getAmount();
11     }
12 
13     log('name:' + _name);
14     log('amount:' + outstanding);
15 };

這個重構比較簡單

 1 var printOwing = function (amount) {
 2     //var productList = [];//這個數據你懂的
 3     var outstanding = 0;
 4     printBanner();
 5 
 6     for (var k in productList) {
 7         outstanding += productList[k].getAmount();
 8     }
 9 
10     log('name:' + _name);
11     log('amount:' + outstanding);
12 };
13 
14 var printBanner = function () {
15     log('*****************');
16     log('****Cunstomer Owes*****');
17     log('*****************');
18 };

可是沒有局部變量只是一個傳說,好比上處最後log的內容,因而來一個(簡單的)

 

 1 var printOwing = function (amount) {
 2     //var productList = [];//這個數據你懂的
 3     var outstanding = 0;
 4     printBanner();
 5 
 6     for (var k in productList) {
 7         outstanding += productList[k].getAmount();
 8     }
 9 
10     printDetails(outstanding);
11 };
12 
13 var printBanner = function () {
14     log('*****************');
15     log('****Cunstomer Owes*****');
16     log('*****************');
17 };
18 
19 var printDetails = function (outstanding) {
20     log('name:' + _name);
21     log('amount:' + outstanding);
22 }

PS:此處的_name,在js裏面應該是this._name

這個也相對比較簡單,若是局部變量是個對象,而被提煉代碼調用了會對該對象形成修改的函數,也能夠這樣作,只不過須要將這個對象做爲參數傳遞給目標函數,只有在被提煉函數會對變量賦值時,有所不一樣,下面咱們就會看到這個狀況。

局部變量賦值

這個狀況較複雜,這裏咱們看看臨時變量被修改的兩種狀況,

比較簡單的狀況是這個變量只在被提煉代碼段中使用,這樣源代碼中的這個變量就能夠被消除,

另外一種狀況就是源代碼中改了,提煉處代碼也改了, 這個時候若是是以前改的就不用管了,以後會發生變化須要返回這個值。

這裏咱們將上述代碼計算的代碼提煉出來:

 1 var log = function (msg) { console.log(msg); };
 2 var printOwing = function (amount) {
 3     //var productList = [];//這個數據你懂的
 4     printBanner();
 5     printDetails(getOutStanding());
 6 };
 7 
 8 var printBanner = function () {
 9     log('*****************');
10     log('****Cunstomer Owes*****');
11     log('*****************');
12 };
13 
14 var printDetails = function (outstanding) {
15     log('name:' + _name);
16     log('amount:' + outstanding);
17 };
18 
19 var getOutStanding = function () {
20     var result = 0;
21     for (var k in productList) {
22         result += productList[k].getAmount();
23     }
24     return result;
25 };

這個例子中outstanding變量只是單純被初始化一個明確的值,但若是其餘地方作過處理,就必須做爲參數傳入

 1 var log = function (msg) { console.log(msg); };
 2 var printOwing = function (amount) {
 3     //var productList = [];//這個數據你懂的
 4     var outstanding = amount * 2;
 5     outstanding = getOutStanding(outstanding)
 6     printBanner();
 7     printDetails(getOutStanding());
 8 };
 9 
10 var printBanner = function () {
11     log('*****************');
12     log('****Cunstomer Owes*****');
13     log('*****************');
14 };
15 
16 var printDetails = function (outstanding) {
17     log('name:' + _name);
18     log('amount:' + outstanding);
19 };
20 
21 var getOutStanding = function (result) {
22     result = result || 0;//注意這種寫法若是result爲0可能致使咱們程序BUG,因此數字要注意
23     for (var k in productList) {
24         result += productList[k].getAmount();
25     }
26     return result;
27 };

若是其中改變的變量不止一個,就返回對象變量吧,這個東西就暫時說到這裏了,後面看實例吧。

內聯函數

該方法用於消除函數,先來個代碼看看

 1 var getRating = function () {
 2     return moreThanFive() ? 2 : 1;
 3 };
 4 
 5 var moreThanFive = function () {
 6     return num > 5;
 7 }
 8 
 9 var getRating = function () {
10     return num > 5 ? 2 : 1;
11 };

原本咱們常以簡單的函數表現動做意圖,這樣會使代碼更爲清晰,但有時候會遇到某些函數,內部代碼很簡單,這種狀況就應該去掉這個函數

PS:這個界限不是很好把握,另外一種狀況是手上有一羣組織不合理的函數,咱們能夠將它組織到一個大函數中,再重新提煉成小函數,這種狀況更多見。

若是咱們使用了太多中間層,使得系統全部的函數都是對另外一個函數的委託,這個時候,函數會讓咱們暈頭轉向,這個時候能夠去掉中間層

如何作?

① 檢查函數,肯定其不具備多態(若是有繼承關係就不要搞他了)

② 找出函數全部調用點

③ 複製爲函數本體

④ 檢查,刪除函數自己

內聯函數比較複雜,遞歸調用,多返回點,

內聯臨時變量

你有一個臨時變量,只被一個簡單的表達式賦值一次,而他影響了其它重構手法,那麼將全部對該變量的引用動做,替換爲對它賦值的那個表達式自身

1 var basePrice = anOrder.basePrice();
2 return basePrice > 100;
3 
4 return anOrder.basePrice() > 100

以查詢取代臨時變量

這個方法比較實用,程序以一個臨時變量保存某一個表達式的結果時,那麼將這個表達式提煉到一個獨立的函數中

將這個臨時變量的變量的全部引用點替換爲新函數的調用,這樣的話,新函數就能夠被其它函數使用了

 1 function amount() {
 2     var basePrice = _quantity * _itemPrice;
 3     if (basePrice > 1000) return basePrice * 0.95;
 4     else return basePrice * 0.98;
 5 }
 6 
 7 
 8 function amount() {
 9     if (basePrice() > 1000) return basePrice() * 0.95;
10     else return basePrice() * 0.98;
11 }
12 
13 function basePrice() {
14     return _quantity * _itemPrice;
15 }

這樣作的好處是,消除臨時變量,由於臨時變量是暫時的,只能存在所屬函數,因此爲了可以訪問到變量,有可能咱們會寫出更長的函數,

若是把臨時變量替換爲一個查詢,那麼同一個類中的全部函數均可以得到這個數據,類的結構會更加清晰

查詢替換變量通常會在提煉函數時候用到,該方法要用好仍是不易的

怎麼作?

① 找出只被賦值一次的臨時變量(屢次賦值須要分解臨時變量了),注意這在js中可能不易

② 提煉臨時變量等號右邊到獨立函數

③ 測試

咱們經常使用臨時變量保存循環中的信息,這個狀況下就把整個循環提煉出來,

PS:這個時候咱們可能會關心性能問題,聽說這個性能不會對咱們的程序有多大的影響

 

 1 function getPrice() {
 2     var basePrice = _quantity * _itemPrice;
 3     var discountFactor;
 4     if (basePrice > 1000)
 5         discountFactor = 0.95;
 6     else
 7         discountFactor = 0.98;
 8 }
 9 此時咱們想替換兩個臨時變量
10 function getPrice() {
11     return basePrice() * discountFactor();
12 }
13 
14 function basePrice() {
15     return _quantity * _itemPrice;
16 }
17 
18 function discountFactor() {
19     return basePrice() > 1000 ? 0.95 : 0.98
20 }

引入解釋性變量

若是我有一個複雜表達式,將該表達式(或者一部分)的結果放進一個臨時變量,將此臨時變量名用來解釋表達式用途

 1 if(platform.toLocaleUpperCase().indexOf('MAC') > -1 && location.href.toLocaleUpperCase().indexOf('IE') > -1 && ......){
 2 //do someting
 3 }
 4 
 5 這種很長的條件判斷很難讀,這個時候臨時變量反而能幫助你閱讀
 6 var isMac = platform.toLocaleUpperCase().indexOf('MAC') > -1;
 7 var isIE = location.href.toLocaleUpperCase().indexOf('IE') > -1;//這個代碼有問題,沒必要關注
 8 
 9 if(isMac && isIE && ......){
10 //do someting
11 }

但有個問題是,原做者並不推薦增長臨時變量,因此,通常咱們就會提煉爲函數了,這樣也增長重用性

分解臨時變量

咱們的程序有某個臨時變量被賦值超過一次,他既不是循環變量又不被用於收集計算結果,那麼針對每次賦值新建一個對應的臨時變量

1 var temp = 2 * (_height + _width);
2 temp = _height * _width;
3 
4 var perimeter = 2 * (_height + _width);
5 var area = _height * _width;

這樣作的好處,其實就是爲了不一個變量被無心義屢次使用

除了循環變量或者用於收集結果的臨時變量應該被屢次使用,還有一些臨時變量用於保存冗長的結果會被稍後使用

若是一個變量被賦值超過一次,那麼他就擔任了過多的職責了 其實這樣作的好處,是爲了咱們方便提煉函數,或者以查詢替換變量的操做

 1 function getDistanceTravelled(time) {
 2     var result;
 3     var acc = _primaryForce / _mass;
 4     var primaryTime = Math.min(time, _delay);
 5 
 6     result = 0.5 * acc * primaryTime * primaryTime;
 7     var secondaryTime = time - _delay;
 8 
 9     if (secondaryTime > 0) {
10         var primaryVel = acc * _delay;
11         acc = (_primaryForce + _secondaryForce) / _mass;
12         result += primaryVel * secondaryTime + 0.5 * acc * secondaryTime * secondaryTime;
13     }
14     return result;
15 }

按照做者的話來講,這真是醜陋的代碼啊,我反正抄都抄了好久

這個是一個物理中的一個神馬公式我給忘了

見圖,acc被兩次賦值,第一次是爲了保存第一次力形成的初始速度,

第二次保存兩個力共同做用形成的加速度,這裏咱們使用final替換第二次的結果

 1 function getDistanceTravelled(time) {
 2     var result;
 3     var acc = _primaryForce / _mass;
 4     var primaryTime = Math.min(time, _delay);
 5 
 6     result = 0.5 * acc * primaryTime * primaryTime;
 7     var secondaryTime = time - _delay;
 8 
 9     if (secondaryTime > 0) {
10         var primaryVel = acc * _delay;
11         var final = (_primaryForce + _secondaryForce) / _mass;
12         result += primaryVel * secondaryTime + 0.5 * final * secondaryTime * secondaryTime;
13     }
14     return result;
15 }

而後咱們再使用下其它手段試試: ①提煉函數,②以查詢取代變量

PS:我知道了力/質量=重力加速度

function getDistanceTravelled(time) {
    var result = 0.5 * accelerationOfGravity(_primaryForce, _mass) 
* primaryTime(time, _delay);
    if (secondaryTime() > 0) {
        result += primaryVel(_primaryForce, _mass) * secondaryTime() 
+ 0.5 * final(_primaryForce, _secondaryForce, _mass) * secondaryTime() * secondaryTime();
    }
    return result;
}

function accelerationOfGravity(force, mass) {
    return force / mass;
}
function primaryTime(time, _delay) {
    return Math.min(time, _delay) * Math.min(time, _delay);
}
function secondaryTime() {
    return time - _delay;
}
function primaryVel(_primaryForce, _mass) {
    return accelerationOfGravity(_primaryForce, _mass) * _delay;
}
function final(_primaryForce, _secondaryForce, _mass) {
    return (_primaryForce + _secondaryForce) / _mass;
}

下面是我完成不理解程序狀況下胡亂意淫改的,沒必要在乎

移除參數賦值

代碼對一個參數賦值,那麼以一個臨時變量取代該參數位置(這對js不知道好使不)

1 function discount(inputVal, quantity, yearToDate) {
2     if (inputVal > 50) inputVal -= 2;
3 }
4 //修改後
5 function discount(inputVal, quantity, yearToDate) {
6     var result = inputVal;
7     if (inputVal > 50) result -= 2;
8 }

這樣作的目的主要爲了消除按值傳遞與按引用傳遞帶來的問題,這裏咱們來深刻糾結一番

 

 1 function discount(inputVal, quantity, yearToDate) {
 2     if (inputVal > 50) inputVal -= 2;
 3 }
 4 //修改後
 5 function discount(inputVal, quantity, yearToDate) {
 6     var result = inputVal;
 7     if (inputVal > 50) result -= 2;
 8 }
 9 
10 這樣作的目的主要爲了消除按值傳遞與按引用傳遞帶來的問題,這裏咱們來深刻糾結一番
11 var value = 1;
12 function demoVal(p) {
13     p++;
14 }
15 console.log(value);
16 demoVal(value);
17 console.log(value);
18 
19 
20 var obj = {
21 name: 'yexiaochai',
22 age: 20
23 };
24 function demoRefer(obj) {
25     obj.age++;
26 }
27 console.log(obj);
28 demoRefer(obj);
29 console.log(obj);

因此你懂的,函數內部操做,有時無心就會改變傳入參數

以函數對象取代函數

咱們有一個大型函數,其中對局部變量的使用使你沒法採用提煉函數方法,將這個函數放進一個單獨對象中,如此一來局部變量就成了對象內的字段

而後你能夠在同一個對象中將這個大型函數分解爲多個小型函數 函數分解難度較高,將變量替換爲查詢能夠減少他的難度,若是也不行的話,就將函數對象化吧

這裏來一個至關簡化的代碼:

 1 function gamma(inputVal, quantity, yearToDate) {
 2     var v1 = (inputVal * quantity) + delta();
 3     var v2 = (inputVal * yearToDate) + 100;
 4 
 5     if (yearToDate - v1 > 100) v2 -= 20;
 6     var v3 = v2 * 7;
 7 
 8     //......
 9     return v3 - 2 * v1;
10 }

爲了說明這個問題,做者寫了一段莫名其妙的代碼,我一看,確實莫名其妙......

 1 function delta() {return 10; }
 2 function gamma(inputVal, quantity, yearToDate) {
 3     var v1 = (inputVal * quantity) + delta();
 4     var v2 = (inputVal * yearToDate) + 100;
 5 
 6     if (yearToDate - v1 > 100) v2 -= 20;
 7     var v3 = v2 * 7;
 8 
 9     //......
10     return v3 - 2 * v1;
11 }
12 
13 var Gamma = function (opts) {
14     this.inputVal = opts.inputVal;
15     this.quantity = opts.quantity;
16     this.yearToDate = opts.yearToDate;
17     this.v1;
18     this.v2;
19     this.v3;
20 };
21 Gamma.prototype = {
22     computer: function () {
23         this.alter1();
24         this.alter2();
25         if (this.yearToDate - this.v1 > 100) this.v2 -= 20;
26         this.v3 = this.v2 * 7;
27 
28         //......
29         return this.v3 - 2 * this.v1;
30     },
31     alter1: function () {
32         this.v1 = (this.inputVal * this.quantity) + delta();
33     },
34     alter2: function () {
35         this.v2 = (this.inputVal * this.yearToDate) + 100;
36     }
37     //....
38 
39 };
40 
41 //demo
42 console.log(gamma(5, 6, 7));//865
43 
44 var d = new Gamma({
45 inputVal: 5, 
46 quantity: 6, 
47 yearToDate: 7});
48 console.log(d.computer());//865

替換算法

替換算法實際上是最難的,在你不熟悉代碼業務與邏輯時,你去改的話,兄弟大家就坑吧!!! 來個簡單的例子結束今天的任務

 1 function find(data) {
 2     if (data == 1) return '週日';
 3     if (data == 2) return '週一';
 4 
 5     //...
 6     return null;
 7 }
 8 
 9 function find(data) {
10             

11     return ['週日', '週一', /*......*/][data];
12 }

 

OK,進入任務結束,高高興興打遊戲了!

相關文章
相關標籤/搜索