重构过程当中,仍是有必定标准可循的,每一个重构手法有以下五个部分: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,进入任务结束,高高兴兴打游戏了!