重构
重新组织函数
1.Extract Method(提炼函数)
a. 原因
如果有一个过长的函数或者需要一段注释才能让人理解用途的代码,那么就将这段代码放进一个独立函数中。
b. 好处
函数粒度小,被复用的机会大
使高层函数读起来像注释
函数都是细粒度,更易被复写
c. 步骤
1. 创建一个新函数,根据这个函数的意图来对他命名
2. 即使你想要提炼的代码非常简单,例如只是一条消息或一个函数调用,只要新函数的名称能够以更好方式昭示代码意图,你也应该提炼他。但如果你想不出一个更有意义的名称,就别动
3. 将提炼的代码从源码函数复制到新建的目标函数中。
4. 仔细检查提炼出来的代码,看看其中是否引用了“作用域限于原函数”的变量(包括局部变量和原函数参数)
5. 检查是否有“仅用于被提炼代码段”的临时变量。如果有,在目标函数中将他们声明为临时变量。
6. 检查被提炼代码段,看看是否有任何局部变量的值被它改变。如果一个临时变量值被修改了,看看是否可以将被提炼代码段处理为一个查询,并将结果赋值给相关变量。如果很难这样做,或如果被修改的变量不止一个,你就不能仅仅将这段代码原封不动的提炼出来,你可能需要先分解临时变量,然后再次尝试提炼。也可以以查询替代临时变量。
7. 将被提炼代码段中年需要读取的局部变量,当做参数传给目标函数。
8. 处理完所有局部变量之后,进行编译。
9. 在源函数中,将被提炼代码段替换为对目标函数的调用。(如果你将任何临时变量移到目标函数中,请检查他们原本的声明式是否在被提炼代码段的外围,如果是,现在你就可以删除这些声明式了)
10. 编译,测试。
d. 例子
1. Receipt
2. 无局部变量
3. 有局部变量
4. 对局部变量再赋值
e. 做法总结
1. idea快捷键
2. 即使提炼的代码非常简单,例如只是一个消息或者函数引用,只要新函数的名称能够 以更好的方式昭示代码意图,就应该提炼他
3. 在代码编写规范里面把这个作为参考,比如一个方法最多不能超过多少行等等。这在一定程度上也能使程序员把这些复杂的逻辑剥离成意义很清楚的小方法。
2.Inline Method(内联函数)
a. 原因/好处
本来我们常以简单的函数表现动作意图,这样会使代码更为清晰,但有时候会遇到某些函数,内部代码很简单,这种情况就应该去掉这个函数
b. 步骤
1. 检查函数,确定其不具有多态(如果有继承关系就不要搞他了)
2. 找出函数所有调用点
3. 复制为函数本体
4. 检查,删除函数本身
c. 例子
var getRating = function () {
return moreThanFive() ? 2 : 1;
};
var moreThanFive = function () {
return num > 5;
}
var getRating = function () {
return num > 5 ? 2 : 1;
};
d. 做法总结
1. 这个界限不是很好把握,另一种情况是手上有群组织不合理的函数,我们可以将它组织到一个大函数中,再从新提炼成小函数,这种情况更多见
2. 如果我们使用了太多中间层,使得系统所有的函数都是对另一个函数的委托,这个时候,函数会让我们晕头转向,这个时候可以去掉中间层
3.Inline Temp(内联临时变量)
a. 原因
有一个临时变量,只被一个简单的表达式赋值一次,而他影响了其它重构手法,那么将所有对该变量的引用动作,替换为对它赋值的那个表达式自身
b. 例子
var basePrice = anOrder.basePrice();
return basePrice > 100;
return anOrder.basePrice() > 100
4.以查询取代临时变量
a. 原因
临时变量的问题在于:他们是暂时的,而且只能在所属函数中使用,会驱使你写出更长的函数,
b. 好处
如果把临时变量替换为一个查询,那么同一个类中的所有函数都可以获得这个数据,类的结构会更加清晰
c. 步骤
1. 找出只被赋值一次的临时变量(多次赋值需要分解临时变量了),注意这在js中可能不易
2. 提炼临时变量等号右边到独立函数
3. 测试
d. 例子
---------- 不提取的 ----------
function amount() {
var basePrice = _quantity * _itemPrice;
if (basePrice > 1000) return basePrice * 0.95;
else return basePrice * 0.98;
}
---------- 不提取的 ----------
---------- 提取的 ----------
function amount() {
if (basePrice() > 1000) return basePrice() * 0.95;
else return basePrice() * 0.98;
}
function basePrice() {
return _quantity * _itemPrice;
}
---------- 提取的 ----------
e. 做法总结
---------- 不提取的修改 ----------
function getPrice() {
var basePrice = _quantity * _itemPrice;
var discountFactor;
if (basePrice > 1000)
discountFactor = 0.95;
else
discountFactor = 0.98;
}
---------- 不提取的修改 ----------
---------- 提取的修改 ----------
function getPrice() {
return basePrice() * discountFactor();
}
function basePrice() {
return _quantity * _itemPrice;
}
function discountFactor() {
return basePrice() > 1000 ? 0.95 : 0.98
}
---------- 提取的修改 ----------
5. 引入解释性变量
a. 原因
如果我有一个复杂表达式,将该表达式(或者一部分)的结果放进一个临时变量,将此临时变量名用来解释表达式用途
b. 例子
-----之前------
if(platform.toLocaleUpperCase().indexOf('MAC') > -1 && location.href.toLocaleUpperCase().indexOf('IE') > -1 && ......){
//do someting
}
-----之前------
-----之后------
var isMac = platform.toLocaleUpperCase().indexOf('MAC') > -1;
var isIE = location.href.toLocaleUpperCase().indexOf('IE') > -1;//这个代码有问题,不必关注
if(isMac && isIE && ......){
//do someting
}
-----之后------
c. 总结
1. 这种很长的条件判断很难读,这个时候临时变量反而能帮助你阅读
2. 但有个问题是,原作者并不推荐增加临时变量,所以,一般我们就会提炼为函数了,这样也增加重用性
6. 分解临时变量
a. 原因
我们的程序有某个临时变量被赋值超过一次,他既不是循环变量又不被用于收集计算结果,那么针对每次赋值新建一个对应的临时变量
var temp = 2 * (_height + _width);
temp = _height * _width;
var perimeter = 2 * (_height + _width);
var area = _height * _width;
b. 好处
1. 其实就是为了避免一个变量被无意义多次使用
2. 除了循环变量或者用于收集结果的临时变量应该被多次使用,还有一些临时变量用于保存冗长的结果会被稍后使用
3. 如果一个变量被赋值超过一次,那么他就担任了过多的职责了 其实这样做的好处,是为了我们方便提炼函数,或者以查询替换变量的操作
c. 例子
/**
* acc被两次赋值,第一次是为了保存第一次力造成的初始速度,
* 第二次保存两个力共同作用造成的加速度,这里我们使用final替换第二次的结果
*/
---- 修改前 ------
function getDistanceTravelled(time) {
var result;
var acc = _primaryForce / _mass;
var primaryTime = Math.min(time, _delay);
result = 0.5 * acc * primaryTime * primaryTime;
var secondaryTime = time - _delay;
if (secondaryTime > 0) {
var primaryVel = acc * _delay;
acc = (_primaryForce + _secondaryForce) / _mass;
result += primaryVel * secondaryTime + 0.5 * acc * secondaryTime * secondaryTime;
}
return result;
}
---- 修改前 ------
1. 力/质量=重力加速度
2. 提炼函数 -> 以查询取代变量
---- 修改后 ------
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;
}
---- 修改后 ------
7.移除参数赋值
a. 原因
代码对一个参数赋值,那么以一个临时变量取代该参数位置(这对js不知道好使不)
b. 好处
这样做的目的主要为了消除按值传递与按引用传递带来的问题
c. 例子
function discount(inputVal, quantity, yearToDate) {
if (inputVal > 50) inputVal -= 2;
}
//修改后
function discount(inputVal, quantity, yearToDate) {
var result = inputVal;
if (inputVal > 50) result -= 2;
}
d. 总结
var value = 1;
function demoVal(p) {
p++;
}
console.log(value);
demoVal(value);
console.log(value);
var obj = {
name: 'yexiaochai',
age: 20
};
function demoRefer(obj) {
obj.age++;
}
console.log(obj);
demoRefer(obj);
console.log(obj);
8.以函数对象取代函数
a. 原因
我们有一个大型函数,其中对局部变量的使用使你无法采用提炼函数方法,将这个函数放进一个单独对象中,如此一来局部变量就成了对象内的字段
然后你可以在同一个对象中将这个大型函数分解为多个小型函数 函数分解难度较高,将变量替换为查询可以减小他的难度,如果也不行的话,就将函数对象化吧
b. 例子
ReplaceMethodWithObject
9.替换算法
a.原因
替换算法其实是最难的,在你不熟悉代码业务与逻辑时,
b.例子
function find(data) {
if (data == 1) return '周日';
if (data == 2) return '周一';
//...
return null;
}
function find(data) {
return ['周日', '周一', /*......*/][data];
}
文章永久链接:https://tech.souyunku.com/34540