前言
重构过程中,还是有一定标准可循的,每个重构手法有如下五个部分:
首先是名称(name),建造一个重构词汇表,名称是非常重要的 然后是一个简短概要,介绍重构手法适用的场景,以及他干的事情,这样我们可以快速找到所需重构方法
然后,介绍为什么需要这个重构,或者什么情况下适用这个重构做法,简明扼要的介绍如何一步步重构
最后,以一个十分简单的例子说明此重构如何运作
所以今天我们进入重构的学习吧!
提炼函数
我们重构是,重头戏就是处理函数,以js而言,函数重要性更是终于“类”的概念。
如何恰当的包装代码,如何减少过长的代码,这是我们多数时刻需要思考的。
但是要消除函数过长是不易的,一个函数过长说明这个函数所完成的业务很复杂,而且可能关联性很高,要将这样的代码拆分,就不止是重构的事情了
在此提炼函数变得十分考验一个人的水平,如何将一段代码从原先函数提取出来,如何将一个单数调用替换为函数本体,这些都不简单
最后改完,时常发现提炼的某些函数实际意义不大,我们还得考虑如何回溯原来的函数
难在何处
提炼函数不易,难在处理局部变量,临时变量尤其突出
处理一个函数时,我们可以先使用查询取代变量的方法取代临时变量
如果一个临时变量多次使用,可以使用分解临时变量的方法将它变得容易替换
但是,多数时候临时变量确实混乱,难以替换,这时候我们可以使用以函数对象取代函数的方法,这样的代价就会引入新类
参数带来的问题比临时变量少一点,前提是不在函数内为他赋值(不对参数赋值,对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,进入任务结束,高高兴兴打游戏了!
本文转自叶小钗博客园博客,原文链接http://www.cnblogs.com/yexiaochai/p/3371121.html如需转载请自行联系原作者