过长函数
- 含义:
一个函数(方法)里包含了一大堆逻辑,做了很多事。 - 坏处:
可读性差,程序越长越难以理解。 - 目标:
把逻辑整理,分解为不同的小函数。提高可读性 - 实现方法:
- 大部分情况下:
使用 Extract Method(提炼函数) - 如果函数内有大量的参数和临时变量:
- 考虑运用Introduce Parameter Object (引入参数对象) 消除临时元素
- 考虑运用Replace Temp with Query (以查询取代临时变量) 和 Preserve Whole Object(保持对象完整) 可以将过长的参数列变得更简洁一些
- 如果已经使用了上面的方法,但是还有很多变量和参数,可以考虑使用 Replace Method with Method Object(以函数对象取代函数)。
- 如何确认提炼哪一段代码:
- 寻找代码中的注释,一般有注释就说明那一段逻辑可以被提炼。
- 条件表达式和循环通常也是可以提炼的信号,考虑使用 Decompose Conditional(分解条件表达式)
- 大部分情况下:
本文涉及的重构方法
Extract Method (提炼函数)
Introduce Parameter Object (引入参数对象)
- 作用:
消除不同方法中经常出现的相同入参变量,使代码更精炼易读 - 步骤:
- 新建一个类,表示你想替换的一组参数,并将这个类设为不可变的。
- 针对使用这些参数的所有函数,使用Add Parameter(添加参数) ,传入上述新建类的实例对象,并把这些参数写作不可变的。
- 把原函数中的入参移除,改为通过新类来当作入参
- 编译并测试,可以观察有没有某些方法也放入到这个参数对象中。可以考虑使用 Move Method(搬移函数),搬移前还可以考虑先用 Extract Method(提炼函数) 来将函数提炼出来。
- 代码实例
//使用Introduce Parameter Object前
calculateDuration(Date start, Date end){
//计算时间间隔
}
//使用Introduce Parameter Object后
calculateDuration(DateRange dateRange){
//计算时间间隔
}
//DateRange类
class DateRange {
//把参数改为不可变的
private final Date start;
private final Date end;
DateRange(Date start, Date end){
this.start = start;
this.end = end;
}
Date getStart(){
return start;
}
Date getEnd(){
return end;
}
- 重构思路:
某些函数的入参很多,而且很相似,可以考虑为这些入参建立一个类,然后把这个函数的入参改为这个类。
Replace Temp with Query (以查询取代临时变量)
- 作用:
代码中的临时变量替换为获取该临时变量的方法,使代码更清晰易读。 - 步骤:
- 找出代码中只被赋值一次的临时变量。(如果某个临时变量被赋值超过一次,考虑使用 Split Temporary Variable(分解临时变量) 将其分割成多个变量)
- 将该临时变量声明为final,编译测试一下,保证只被赋值一次。
- 把临时变量的计算式放到一个独立的方法(函数)中,首先考虑声明为private,如果后面发现有更多类使用,再放松它的作用域。
- 确保该方法不会修改任何对象的内容,如果有,可以考虑使用 Separate Query from Modifier(将查询函数跟修改函数分开)
- 在该临时变量商考虑使用 Inline Temp(内联临时变量)
- 代码实例
//getPrice方法:根据根据原价price和因数factor算打折力度
//假设外面有两个变量factor 和price
double getPrice() {
int basePrice = factor * price; //basePrice假设为现价
double discountFactor;
if (basePrice > 1000) //如果现价大于1000块,打95折,否则打98折
discountFactor = 0.95;
else
discountFactor = 0.98;
return basePrise * discountFactor; //返回最终价格
}
//重构后
double getPrice() {
return basePrice() * discountFactor();
}
private int basePrice() {
return factor * price;
}
private double discountFactor() {
return basePrice() > 1000 ? 0.95 : 0.98;
- 重构思路:
临时变量做成一个方法,使其可以被引用,简化主要代码的代码量,提升可读性。
Preserve Whole Object (保持对象完整)
- 作用:
函数直接传入对象,避免入参频繁变动,增加代码可读性。 - 步骤:
- 如果入参是来自于某个对象的某个值,把这个入参改为这个对象。
- 把用这个入参的方法中的对应值引用改为从这个对象获取这个对应值。
- 编译测试
- 代码实例
//改变前
int low = daysTempRange().getLow();
int high = daysTempRange().getHigh();
within = plan.within(low, high);
//改变后
within = plan.within(daysTempRange());
-
重构思路:
如果一个方法的入参全部或部分都来自于某个对象,可以考虑改为直接传入该对象。 -
建议:
此方法传入了对象,需要考虑性能和函数内分解逻辑是否复杂,酌情使用。
Replace Method with Method Object (用函数对象取代函数)
- 作用:
把一个局部变量很多的长函数(方法),分解成多个小函数。增加代码可读性 - 步骤:
- 建立一个新的类,根据待处理函数的用途来命名。
- 新类中建立一个final字段,用来保存原函数所在的对象,针对原来函数的每个临时变量和参数,在新类里建立一个对应的字段保存。
- 在新类中建立构造函数,接收那个final声明的对象
- 在新类中建立一个compute()函数,把原函数的代码复制到里面
- 编译测试
- 代码实例
//改变前
class SuperComputer {
int binaryCom (int bi, int factor, int cg) {
int baseVal = (bi * factor) + con();
int secVal = (bi * cg) + 100;
if( (cg - baseVal) > 200)
secVal -= 20;
int thrdVal = secVal * 17;
//其他复杂的计算逻辑.......
return thrdVal - 2 * baseVal;
}
}
//改变后
class SuperComputer {
int binaryCom (int bi, int factor, int cg) {
//原函数改为创建新类并调用compute方法
return new BinaryCalculator(this, bi, factor, cg).compute();
}
}
//把局部变量分离出来,作为一个类
class BinaryCalculator {
private final SuperComputer sc;
private int bi;
private int factor;
private int cg;
private int baseVal;
private int secVal;
private int thrdVal;
//提供构造函数
BinaryCalculator(SuperComputer scArg, int biArg, int factorArg, int cgArg) {
sc = scArg;
bi = biArg;
factor = factorArg;
cg = cgArg;
}
//处理原来的逻辑
int compute() {
baseVal = (bi * factor) + sc.con();
secVal = (bi * cg) + 100;
reduceByCgAndBaseVal();
thrdVal = secVal * 17;
return thrdVal - 2 * baseVal;
}
//这里使用了Extract Method把原有逻辑提炼成小函数,
void reduceByCgAndBaseVal() {
if( (cg - baseVal) > 200)
secVal -= 20;
}
}
- 重构思路:
新建一个类把原来函数里的局部变量抽出来,提供构造方法传入原来的对象和参数,提供compute()函数来处理原来的逻辑,最后把原来的代码替换为这个类调用compute()的代码。
Decompose Conditional (分解条件表达式)
- 作用:
使if的判断逻辑更加清晰,增加代码易读性。 - 步骤:
- 把if语句中的判断条件提炼出来,组成一个函数(方法)。
- 把else中的代码段落也提炼出来,组成一个函数。
- 如果有嵌套的条件逻辑,考虑使用 Replace Nested Conditional with Guard Claused(以卫语句取代嵌套条件表达式),如果也不行,考虑分解其中的每一个条件。
- 编译测试
- 代码实例
//改变前
if((a * b >100 && a - b < 50) || a == 0) {
theVal = a + b;
else
theVal = a - b + 20;
}
//改变后
if(needSum(a,b)) {
theVal = sum(a,b);
else
theVal = needNotSum(a,b);
}
private boolean needSum(int a, int b) {
return (a * b >100 && a - b < 50) || a == 0;
}
private int sum(a, b) {
return a + b;
}
private int needNotSum(a,b) {
return a - b + 20;
}
-
重构思路:
把if中的表达式提炼成方法,把if中的代码和else中的也提炼成方法。 -
建议:
根据if判断语句和条件式中的代码逻辑复杂与否酌情考虑。