坏味道——过长方法(Long Method)
特征
一个方法含有太多行代码。一般来说,任何方法超过 10 行时,你就可以考虑是不是过长了。
函数中的代码行数原则上不要超过 100 行。
问题的原因
通常情况下,创建一个新方法的难度要大于添加功能到一个已存在的方法。大部分人都觉得:“我就添加这么两行代码,为此新建一个方法实在是小题大做了。”于是,张三加两行,李四加两行,王五加两行。。。方法日益庞大,最终烂的像一锅浆糊,再也没人能完全看懂了。于是大家就更不敢轻易动这个方法了,只能恶性循环的往其中添加代码。所以,如果你看到一个超过200行的方法,通常都是多个程序员东拼西凑出来的。
解决方法
一个很好的技巧是:寻找注释。添加注释,一般有这么几个原因:代码逻辑较为晦涩或复杂;这段代码功能相对独立;特殊处理。
如果代码前方有一行注释,就是在提醒你:可以将这段代码替换成一个函数,而且可以在注释的基础上给这个函数命名。如果方法有一个描述恰当的名字,就不需要去看内部代码究竟是如何实现的。就算只有一行代码,如果它需要以注释来说明,那也值得将它提炼到独立函数中。
- 为了给一个方法瘦身,可以使用
提炼方法(Extract Method)
。 - 如果局部变量和参数干扰提炼方法,可以使用
以查询取代临时变量(Replace Temp with Query)
,引入参数对象(Introduce Parameter Object)
或保持对象完整(Preserve Whole Object)
。 - 如果前面两条没有帮助,可以通过
以函数对象取代函数(Replace Method with Method Object)
尝试移动整个方法到一个独立的对象中。 - 条件表达式和循环常常也是提炼的信号。对于条件表达式,可以使用
分解条件表达式(Decompose Conditional)
。至于循环,应该使用提炼方法(Extract Method)
将循环和其内的代码提炼到独立函数中。
收益
- 在所有类型的面向对象代码中,方法比较短小精悍的类往往生命周期较长。一个方法越长,就越不容易理解和维护。
- 此外,过长方法中往往含有难以发现的重复代码。
性能
是否像许多人说的那样,增加方法的数量会影响性能?在几乎绝大多数情况下,这种影响是可以忽略不计,所以不用担心。
此外,现在有了清晰和易读的代码,在需要的时候,你将更容易找到真正有效的方法来重组代码和提高性能。
重构方法说明
提炼方法(Extract Method)
问题
你有一段代码可以组织在一起。
void printOwing() {
printBanner();
//print details
System.out.println("name: " + name);
System.out.println("amount: " + getOutstanding());
}
解决
移动这段代码到一个新的方法中,使用方法的调用来替代老代码。
void printOwing() {
printBanner();
printDetails(getOutstanding());
}
void printDetails(double outstanding) {
System.out.println("name: " + name);
System.out.println("amount: " + outstanding);
}
以查询取代临时变量(Replace Temp with Query)
问题
将表达式的结果放在局部变量中,然后在代码中使用。
double calculateTotal() {
double basePrice = quantity * itemPrice;
if (basePrice > 1000) {
return basePrice * 0.95;
}
else {
return basePrice * 0.98;
}
}
解决
将整个表达式移动到一个独立的方法中并返回结果。使用查询方法来替代使用变量。如果需要,可以在其他方法中合并新方法。
double calculateTotal() {
double basePrice = quantity * itemPrice;
if (basePrice > 1000) {
return basePrice * 0.95;
}
else {
return basePrice * 0.98;
}
}
引入参数对象(Introduce Parameter Object)
问题
你的方法中包含重复的参数组。
![img](https://raw.githubusercontent.com/atlantis1024/JavaParty/master/images/%E7%BC%96%E7%A8%8B/%E9%AB%98%E6%95%88%E7%BC%96%E7%A8%8B/%E9%87%8D%E6%9E%84/long-method/Introduce Parameter Object - Before.png)
解决
使用一个对象来代替这些参数。
![img](https://raw.githubusercontent.com/atlantis1024/JavaParty/master/images/%E7%BC%96%E7%A8%8B/%E9%AB%98%E6%95%88%E7%BC%96%E7%A8%8B/%E9%87%8D%E6%9E%84/long-method/Introduce Parameter Object - After.png)
保持对象完整(Preserve Whole Object)
问题
你获得一个对象的几个参数,然后将这些参数传入一个方法。
int low = daysTempRange.getLow();
int high = daysTempRange.getHigh();
boolean withinPlan = plan.withinRange(low, high);
解决
尝试将整个对象传入方法。
boolean withinPlan = plan.withinRange(daysTempRange);
以函数对象取代函数(Replace Method with Method Object)
问题
你有一个过长方法,它的局部变量交织在一起,以致于你无法应用提炼方法(Extract Method) 。
class Order {
//...
public double price() {
double primaryBasePrice;
double secondaryBasePrice;
double tertiaryBasePrice;
// long computation.
//...
}
}
解决
将方法移到一个独立的类中,使得局部变量成了这个类的字段。然后,你可以将方法分割成这个类中的多个方法。
class Order {
//...
public double price() {
return new PriceCalculator(this).compute();
}
}
class PriceCalculator {
private double primaryBasePrice;
private double secondaryBasePrice;
private double tertiaryBasePrice;
public PriceCalculator(Order order) {
// copy relevant information from order object.
//...
}
public double compute() {
// long computation.
//...
}
}
分解条件表达式(Decompose Conditional)
问题
你有复杂的条件表达式。
if (date.before(SUMMER_START) || date.after(SUMMER_END)) {
charge = quantity * winterRate + winterServiceCharge;
}
else {
charge = quantity * summerRate;
}
解决
根据条件分支将整个条件表达式分解成几个方法。
if (notSummer(date)) {
charge = winterCharge(quantity);
}
else {
charge = summerCharge(quantity);
}