重构不直接影响产品性能,也不直接影响最终的用户感受。重构的直接受益者是软件开发人员,它降低了软件的维护难度,从而减少bug产生率,对提升产品质量具有深刻的意义。重构是一种工匠精神,决定代码的工艺水平。
提升代码质量的基本方法:
1、提炼方法Extract Method
大段复杂的代码不利于后期维护,如果业务变更,代码修改起来耗时耗力,还容易出bug,所以需要精简长代码。
首先要提炼出代码的骨架,大骨架不动,将具体的业务分离出来,独立成方法。这样代码的逻辑容易读懂,局部业务发生变化时,只需要修改对应的方法即可。《重构》P5示例代码:
...
switch(each.getMovie().getPriceCode()){
case Movie.REGULAR:
thisAmount += 2;
if(each.getDaysRented() > 2){
thisAmount += (each.getDaysRented()-2)*1.5;
}
break;
case Movie.NEW_RELEASE;
thisAmount += each.getDaysRented()*3;
break;
case Movie.NEW_CHILDRENS;
thisAmount += 1.5;
if(each.getDaysRented() > 3){
thisAmount += (each.getDaysRented()-3)*1.5;
}
break;
}
...
假如这段计算费用的逻辑在多处被使用,可以提炼成方法。如果计算费用的算法发生变化,只需要修改该方法即可。
2、归类方法(Move Method)
如果一个类和另一个类有太多合作而形成高度耦合,就可以考虑将部分方法过程提炼出来,转移到包含方法中变量值的类中,降低代码耦合度。
例如P19中将计算费用的方法转移到Rental类中,Rental类中包含计算费用的属性值和属性对象,降低了Rental和Customer的耦合度,代码关系也清晰易懂。
3、去除临时变量(Replace Temp With Query)
在函数中,如果临时变量过多,在函数过程赋值、传递,会造成代码冗长而复杂。可以将临时变量的赋值或者计算过程提炼成方法,降低代码的阅读复杂度。
例如P27中,在一个循环中同时处理费用和积分的计算,代码揉和在一起,代码逻辑不清晰,如下:
...
while(rentals.hasMoreElements()){
Rental each = (Rental) rentals.nextElement();
frequentRenterPoints += each.getFrequentRenterPoints();
result += "\t" + each.getMovie().getTitle() + "\t" + String.valueOf(each.getCharge()) + "\n";
totalAmount += each.getCharge();
}
...
可以将费用计算和积分计算的逻辑分成2个逻辑单一的方法,如下:
private double getTaotalCharge(){
double result = 0;
Enumeration rentals = _rentals.elements();
while(rentals.hasMoreElements()){
Rental each = (Rental) rentals.nextElement();
result += each.getCharge()
}
return result;
}
private double getTaotalFrequentRenterPoints(){
int result = 0;
Enumeration rentals = _rentals.elements();
while(rentals.hasMoreElements()){
Rental each = (Rental) rentals.nextElement();
result += each.getFrequentRenterPoints()
}
return result;
}
public String statement(){
double totalAmount = 0;
int frequentRenterPoints = 0;
Enumeration rentals = _rentals.elements();
String result = "Rental Record for " + getName() +""\n;
while(rentals.hasMoreElements()){
double thisAmount = 0;
Rental each = (Rental) rentals.nextElements();
switch(each.getMovie().getPriceCode()){
case Movie.REGULAR:
thisAmount += 2;
if(each.getDaysRented() > 2){
thisAmount += (each.getDaysRented()-2)*1.5;
}
break;
case Movie.NEW_RELEASE;
thisAmount += each.getDaysRented()*3;
break;
case Movie.NEW_CHILDRENS;
thisAmount += 1.5;
if(each.getDaysRented() > 3){
thisAmount += (each.getDaysRented()-3)*1.5;
}
break;
}
frequentRenterPoints++;
if((each.getMovie().getPriceCode()==Movie.NEW_RELEASE) && each.getDaysRented()>1) frequentRenterPoints ++;
result += "\t" + each.getMovie().getTitle() + "\t" + String.valueOf(thisAmount) + "\n";
totalAmount += thisAmount;
}
result += "Amount owed is" + String.valueOf(totalAmount) + "\n";
result += "You earned" + String.valueOf(frequentRenterPoints) + " frequent renter points";
return result;
}