简单易懂读《重构》 - Long Method(过长的函数)

过长函数

  • 含义:
    一个函数(方法)里包含了一大堆逻辑,做了很多事。
  • 坏处:
    可读性差,程序越长越难以理解。
  • 目标:
    把逻辑整理,分解为不同的小函数。提高可读性
  • 实现方法:
    • 大部分情况下:
      使用 Extract Method(提炼函数)
    • 如果函数内有大量的参数和临时变量:
      • 考虑运用Introduce Parameter Object (引入参数对象) 消除临时元素
      • 考虑运用Replace Temp with Query (以查询取代临时变量)Preserve Whole Object(保持对象完整) 可以将过长的参数列变得更简洁一些
      • 如果已经使用了上面的方法,但是还有很多变量和参数,可以考虑使用 Replace Method with Method Object(以函数对象取代函数)
    • 如何确认提炼哪一段代码:
      • 寻找代码中的注释,一般有注释就说明那一段逻辑可以被提炼。
      • 条件表达式和循环通常也是可以提炼的信号,考虑使用 Decompose Conditional(分解条件表达式)

本文涉及的重构方法

Extract Method (提炼函数)
Introduce Parameter Object (引入参数对象)
  • 作用:
    消除不同方法中经常出现的相同入参变量,使代码更精炼易读
  • 步骤:
  1. 新建一个类,表示你想替换的一组参数,并将这个类设为不可变的。
  2. 针对使用这些参数的所有函数,使用Add Parameter(添加参数) ,传入上述新建类的实例对象,并把这些参数写作不可变的。
  3. 把原函数中的入参移除,改为通过新类来当作入参
  4. 编译并测试,可以观察有没有某些方法也放入到这个参数对象中。可以考虑使用 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 (以查询取代临时变量)
  • 作用:
    代码中的临时变量替换为获取该临时变量的方法,使代码更清晰易读。
  • 步骤:
  1. 找出代码中只被赋值一次的临时变量。(如果某个临时变量被赋值超过一次,考虑使用 Split Temporary Variable(分解临时变量) 将其分割成多个变量)
  2. 将该临时变量声明为final,编译测试一下,保证只被赋值一次。
  3. 把临时变量的计算式放到一个独立的方法(函数)中,首先考虑声明为private,如果后面发现有更多类使用,再放松它的作用域。
  4. 确保该方法不会修改任何对象的内容,如果有,可以考虑使用 Separate Query from Modifier(将查询函数跟修改函数分开)
  5. 在该临时变量商考虑使用 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 (保持对象完整)
  • 作用:
    函数直接传入对象,避免入参频繁变动,增加代码可读性。
  • 步骤:
  1. 如果入参是来自于某个对象的某个值,把这个入参改为这个对象。
  2. 把用这个入参的方法中的对应值引用改为从这个对象获取这个对应值。
  3. 编译测试
  • 代码实例
//改变前
int low = daysTempRange().getLow();
int high = daysTempRange().getHigh();
within = plan.within(low, high);

//改变后
within = plan.within(daysTempRange());
  • 重构思路:
    如果一个方法的入参全部或部分都来自于某个对象,可以考虑改为直接传入该对象。

  • 建议:
    此方法传入了对象,需要考虑性能和函数内分解逻辑是否复杂,酌情使用。

Replace Method with Method Object (用函数对象取代函数)
  • 作用:
    把一个局部变量很多的长函数(方法),分解成多个小函数。增加代码可读性
  • 步骤:
  1. 建立一个新的类,根据待处理函数的用途来命名。
  2. 新类中建立一个final字段,用来保存原函数所在的对象,针对原来函数的每个临时变量和参数,在新类里建立一个对应的字段保存。
  3. 在新类中建立构造函数,接收那个final声明的对象
  4. 在新类中建立一个compute()函数,把原函数的代码复制到里面
  5. 编译测试
  • 代码实例
//改变前
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的判断逻辑更加清晰,增加代码易读性。
  • 步骤:
  1. 把if语句中的判断条件提炼出来,组成一个函数(方法)。
  2. 把else中的代码段落也提炼出来,组成一个函数。
  3. 如果有嵌套的条件逻辑,考虑使用 Replace Nested Conditional with Guard Claused(以卫语句取代嵌套条件表达式),如果也不行,考虑分解其中的每一个条件。
  4. 编译测试
  • 代码实例
//改变前
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判断语句和条件式中的代码逻辑复杂与否酌情考虑。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值