重構過程當中,仍是有必定標準可循的,每一個重構手法有以下五個部分: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,進入任務結束,高高興興打遊戲了!