重构:
在编写完代码后,仔细再去检查,会发现有很多地方都有改进。代码的重构(整理)可以为后期维护带来很好的选择。每个方法的粒度应该尽可能的比较小,这样复用代码重写代码,效率才会偏高。
- 重新组织方法
- 提炼方法
- 内联方法
- 内联临时变量
- 某些时候可以以查询方式取代临时变量
- 引入解释性变量
- 分解临时变量
- 移除对参数的赋值
- 以方法对象取代方法
- 替换算法
重新组织方法
1. 提炼方法
提炼方法是最常用也是用的最多的一种手法。将一个过长的方法,提炼成一个主方法下多个子方法。这样的好处是,在后期如果不需要某个逻辑,直接注释其调用即可,如果再需要时,可以再添回来。如果不需要时删除,需要时再写,就重复了。
例如:
//存在伪代码
public void printBill(double amount) {
double total = 0;
System.out.println("-----------");
System.out.println("------顾客账单-----");
System.out.println("-----------");
for(Order order : orders){
total += order.getPrice();
}
System.out.println("顾客:" + name);
System.out.println("付款总额:" + total);
}
上面这个方法,虽然比较简单,但是依然是可以进行提炼的。提炼结果:
public void printBill(double amount) {
printBillHeader();
double total = getTotal();
printDetail(total);
}
private void printBillHeader(){
System.out.println("-----------");
System.out.println("------顾客账单-----");
System.out.println("-----------");
}
private double getTotal(){
double total = 0;
for(Order order : orders){
total += order.getPrice();
}
return total;
}
private void printDetail(double total){
System.out.println("顾客:" + name);
System.out.println("付款总额:" + total);
}
提炼成这样的结果后,在阅读方面,易于阅读,在后期维护方面,也更易维护。
提炼原则
- 一个大的方法可以进行分解
- 分解后的方法用方法名表达方法主体意图
除了将大的方法分解为小的方法外,也可以重新修改变量名,让其名称更能表达其用意。重构的目的是,让计算机理解的程序修改成人可以理解的程序。只要能够清晰表达方法的意图,不在乎方法名的长短。
2. 内联方法
一个提取的方法本体与它的方法名一样清晰易懂,应该移除这个方法,将它内联到原方法中。
例如:
public int getRating(){
return (moreThanFiveLateDeliveries()) ? 2 : 1;
}
private boolean moreThanFiveLateDeliveries(){
return numberOfLateDeliveries > 5;
}
因为函数本体就很清楚,所以,可以将它内联到调用方法中。
public int getRating(){
return numberOfLateDeliveries > 5 ? 2 : 1;
}
这种情况应该很少遇到。
3. 内联临时变量
方法内存在一个简单变量,只被赋值一次,使用一次。
double basePrice = order.getBasePrice();
if (basePrice > 100){}
改变后:
if (order.getBasePrice() > 100) {}
4. 某些时候可以以查询方式取代临时变量
某些时候可以把临时变量变成一种查询方式,虽然这样会调用多次方法,性能上有点影响,但是有些时候把这些变量分开,会让代码提炼的更简洁。举个例子:
public double getPrice(){
int basePrice = quantity * itemPrice;
double discountFactor;
if (basePrice > 1000) {
discountFactor = 0.95;
} else {
discountFactor = 0.98;
}
return basePrice * discountFactor;
}
似乎好像没有哪个地方可以提炼的,因为一个临时变量贯穿了整个方法,没有必要把它们分开。但是如果把这个临时变量定义为一个方法的话,就比较好提炼了。
public double getPrice(){
return getBasePrice() * getDiscountFactor();
}
public int getBasePrice(){
return quantity * itemPrice;
}
public double getDiscountFactor(){
if (getBasePrice() > 1000) {
return discountFactor = 0.95;
}
return discountFactor = 0.98;
}
有时候如果一个临时变量比较简单,不涉及太多查询的话,这种方式是可以采取的,因为调用一个方法来获取的时间,基本可以忽略。如果这个值是从数据库中查询的话,不建议使用,因为每次查数据库,消耗都比较大。
5. 引入解释性变量
例:
if (platform.toUpperCase.indexOf("MAC") != -1 &&
browser.toUpperCase.indexOf("IE") != -1 &&
resize > 0) {
//dosomething
}
变更为:
boolean isMac = platform.toUpperCase.indexOf("MAC") != -1;
boolean isIE = browser.toUpperCase.indexOf("IE") != -1;
if(isMac && isIE && resize > 0){
//dosomething
}
这种方式可以用第一种方式,提炼方法来代替。
6. 分解临时变量
其实就是意义不同不要使用同一个变量。
例如:
double temp = 2 * (width + height);
System.out.println(temp);
temp = width * height;
System.out.println(temp);
要将这个temp分解成两个变量。
double girth = 2 * (width + height);
System.out.println(girth);
double area = width * height;
System.out.println(area);
7. 移除对参数的赋值
也就是说,如果不想改变参数的值,就不要对参数赋值。
8. 以方法对象取代方法
一个比较大的方法,其中局部变量使用方式一的形式进行了提取。如果还有其他比较多的提取方法,将这些方法放入一个单独的类中,其中,局部变量便成了对象的字段,其他方法将是对象的方法。
示例:
public class Account {
public int gamma(int inputValue, int quantity, int yearToData) {
int importValue1 = (inputValue - quantity) * delta();
int importValue2 = inputValue * yearToData + 100;
if (yearToData - importValue1 > 100 ){
importValue2 -= 20;
}
int importValue3 = importValue2 * 7;
return importValue3 - 2 * importValue1;
}
}
把提炼的方法放在一个新的对象中。
public class Gamma {
private Account account;
private int inputValue;
private int quantity;
private int yearToData;
private int importValue1;
private int importValue2;
private int importValue3;
public Gamma(Account source, int inputValue, int quantity, int yearToData){
this.account = source;
this.inputValue = inputValue;
this.quantity = quantity;
this.yearToData = yearToData;
}
public int cumpute(){
//....
}
}
原来的将变为:
public class Account {
public int gamma(int inputValue, int quantity, int yearToData) {
return new Gamma(this, inputValue, quantity, yearToData).cumpute();
}
}
上面是一个简单示例,如果是一个大的方法,可以采用类似的这种方式。这样做的好处是,整个类会变得比较小。阅读起来更加容易。如果一个类中存放一大堆私有方法,第一眼看上去,就很累赘。
9. 替换算法
如果想把某个算法换成更清晰的算法,直接替换。
示例:
String fondPerson(String[] peoples){
for(String people : peoples){
if (people。equals("Don")) {
return "Don";
}
if (people。equals("John")) {
return "John";
}
}
return null;
}
如果要换另一种写法,直接删了原来的,替换新的即可。
String fondPerson(String[] peoples){
String[] exist = {"Don", "John"};
for(String people : peoples){
if(ArrayUtils.contains(exist, people)){
return people;
}
}
return null;
}
上面是对方法进行重构的几种手法。方法的重构是比较频繁的,几乎每个人都能遇到,也是最基本的。只是很多人像我一样,写完代码测试通过之后,再就不想回头去修改自己的代码了。在后期慢慢修改这种坏习惯。勤看自己写过的代码,优化以前的代码,使看起来更易懂,维护起来更方便。