本文基于《重构——改善既有代码的设计(第二版)》创作,目的是对书中罗列的代码重构方法做进一步精炼归纳。
提炼函数(Extract Function)
示例:
void PrintSomething() {
cout << "***********";
cout << "***Begin***";
cout << "***********";
int result = CaculateResult();
cout << result;
cout << "***********";
cout << "****End****";
cout << "***********";
}
--------------after refactoring--------------
void PrintSomething() {
PrintBanner();
int result = CaculateResult();
cout << result;
PrintEnd();
}
void PrintBanner() {
cout << "***********";
cout << "***Begin***";
cout << "***********";
}
void PrintEnd() {
cout << "***********";
cout << "****End****";
cout << "***********";
}
复制代码
提炼函数组为最常用的重构之一,相比于“如何提炼函数”,更重要的是“何时应该把代码放进独立的函数”。作者的观点是“将意图和实现分开”:如果你需要花时间浏览一段代码才能弄清它到底在干什么,那么就应该将其提炼到一个函数中,并根据他所做的事为其命名。
需要注意的是两个点:
- 假如需要提炼的部分很短,却被多次调用,需要担心函数调用带来的性能影响吗?
否,因为短函数常常能让编译器的优化功能运转地更好,因为短函数可以更容易地被缓存。 - 假如一段代码没有重复出现过,仅仅只是因为理解需要一定时间,需要提炼吗?
是,这一阶段的重构不应该过多care性能的消耗,一定的性能消耗换来的 维护/调优空间,在前期还是很赚的。
提炼的做法:
- 创造一个新函数,根据函数的意图(做什么)来对它命名;
- 将待提炼的代码copy到新函数中;
- 检查这部分代码引用变量的作用域;
- 编译->替换->测试。
需提炼部分无局部变量
最简单的重构,无须对提炼部分做任何更改,简单copy封装成新函数就好。
需提炼部分含局部变量
若使用到源函数中的局部变量,但仅仅读而未写,那么作为参数传入就好。
对局部变量再赋值
这一part分成两种情况:
- 若局部变量仅在需提炼部分使用到,那么将该局部变量的声明一并提炼;
- 若局部变量在源函数后续仍被使用到,那么应该作为返回值。
比较哦tricky的是第二种情况:如果返回的变量不止一个,该怎么办?显而易见的方法是构造并返回一个记录对象。但作者认为更好的方法是对局部变量进行重新处理,做查询取代临时变量和拆分变量。(这部分以后说)
内联函数(Inline Function)
内联函数是提炼函数的逆向工程,是将函数解析到调用处。
示例:
int GetRating(int driver) {
return MoreThanFiveLateDeliveries(driver) ? 2 : 1;
}
bool MoreThanFiveLateDeliveries(driver) {
return driver.numberOfLateDeliveries > 5;
}
--------------after refactoring--------------int GetRating(int driver) {
return driver.numberOfLateDeliveries > 5 ? 2 : 1;}
复制代码
内联是很难决定是否使用的一种重构,你可以很容易决定是否对一段代码进行提炼,但很难决定是否内联一个函数。作者将内联的动机定义为:内部代码和函数名称同样清晰可读。
这部分我无法总结了……复杂的函数你不会去内联;简单的函数只要注意一下变量名的改变就好了。
提炼变量(Extract Variable)
当一个表达式冗长、复杂且难以阅读,这种情况下,提炼变量可以帮助有我们将表达式分解为比较容易管理的形式。
return order.quantity * order.itemPrice -
max(0, order.quantity - 500) * order.itemPrice * 0.05 +
min(order.quantity * order.itemPrice * 0.1, 100);
--------------after refactoring--------------const basePrice = order.quantity * order.itemPrice;
const quantityDiscount = max(0, order.quantity - 500) * order.itemPrice * 0.05;
const shipping = min(order.quantity * order.itemPrice * 0.1, 100);
return basePrice - quantityDiscount + shipping;
复制代码
这个重构尤为适用于变量名在更宽的上下文中也有意义的情况(说白了就是别的地方也用得上)。
重点:如果表达式所在的函数位于类中,更好的方式是将局部变量替换成方法。
int getBasePrice {
return order.quantity * order.itemPrice;
}
复制代码
内联变量(Inline Variable)
当表达式并不复杂,则应该通过内联手法消除变量,虽然我个人认为这样可能会导致大量重复的计算,如控制循环结束的变量len = s.length();
但作者的理由是变量可能妨碍重构附近的代码。
虽然这一步很简单,但作者提到的做法中有一小步很精髓:
“如果变量没有被声明为不可修改,先将其变为不可修改,并执行测试,目的是确保该变量只被赋值一次。”
说实话有种大道至简的感觉了,如果让我来那真的是肉眼去看是否有被二次赋值了……
改变函数声明(Change Function Declaration)
当函数名不够直观,或需要修改参数列表时,则需要用到这个重构。它更像是一套流程规范而不是一个具体优化代码的手段。
当只需要修改函数名时,可以直接修改,当然也可以使用迁移式的做法,它更适用于需要修改函数参数的情况。
- 对函数体内部进行提炼,使得在调用处不修改的前提下改变函数声明;
- 对提炼后的函数进行修改,测试;
- 确认修改无误后,将调用处函数名修改为新的函数名。