《重构 改善既有代码的设计》 读书笔记(二十)

6.4 以查询取代临时变量(Replace Temp with Query)

接着上一篇往下讲。
做法

简单情况的步骤:

  1. 找出只被赋值一次的临时变量。

我们之前说过,如果临时变量被赋值多次,那么意味着它的分割不会有任何影响,我们可以使用分解临时变量(6.6 Split Temporary Variable)来分割。
这样的好处就是,让结构更清晰,更好被提炼。

  1. 将只被赋值过一次的变量声明为final。

如果它的确只被赋值过一次,那么这样做不会报错,否则会报错。
这并不是必须要做的步骤,这只是为了确保不会有错。

  1. 编译。

  2. 把临时变量被赋值的右侧的表达式提炼成一个独立函数出来。

通常情况,一开始提炼函数时总会将其设置成private类型。只有在之后发现其他类也有要调用此方法的情况,才会变成public修饰。
在提炼函数时,要确保提炼出的函数并不会修改任何对象内容。如果有修改,那么需要进行将查询函数和修改函数分离(10.4 Separate Query from Modifier)。

将查询函数和修改函数分离(10.4 Separate Query from Modifier):之前说过,这可以理解成getter和setter,现在再往深里看一下。如果一个函数有返回值,那么它的内部就不应该有修改对象内容的操作,真的发生的时候,就需要将返回和修改分成两个部分。

  1. 编译,测试。

  2. 在该临时变量身上实施内联临时变量6.3 Inline Temp),将那个临时变量替换成方法形式。

我们常常使用临时变量保存循环中的累加信息,这种情况下,循环体可以被提炼函数,原方法中的这几句循环就能被简单的一句调用方法替换。

有时,循环内部不仅仅累加一个临时变量的值,此时应该多次重复这次循环,每次循环内部只累加一个值。(此处性能存在问题)

我们可以暂时不需要考虑性能,除非它真的慢得受不了,那个时候我们再把之前的步骤撤销掉。

范例

double getPrice() {
	int basePrice = quantity * itemPrice;
	double discountFactor;
	if (basePrice > 1000)
		discountFactor = 0.95;
	else
		discountFactor = 0.98;
	return basePrice * discountFactor;
}

>>> 为保证即将修改的临时变量仅被赋值一次,使用final修饰
double getPrice() {
	final int basePrice = quantity * itemPrice;
	double discountFactor;
	if (basePrice > 1000)
		discountFactor = 0.95;
	else
		discountFactor = 0.98;
	return basePrice * discountFactor;
}

>>> 率先修改basePrice变量,需要提炼右侧表达式
double getPrice() {
	final int basePrice = basePrice();
	...
}
int basePrice() {
	return quantity * itemPrice;
}

>>> 内联临时变量并去除原先的那个临时变量basePrice
double getPrice() {
	final double discountFactor;
	if (basePrice() > 1000)
		discountFactor = 0.95;
	else
		discountFactor = 0.98;
	return basePrice() * discountFactor;
}

接下来处理另一个临时变量discountFactor:

double getPrice() {
	final double discountFactor = discountFactor();
	if (basePrice() > 1000)
		discountFactor = 0.95;
	else
		discountFactor = 0.98;
	return basePrice() * discountFactor;
}
double discountFactor() {
	if(basePrice() > 1000)
		return 0.95;
	else
		return 0.98;
}

>>> 此时,编译报错了:因为在上方的discountFactor被赋值了不止一次。
> 事实上,我们已经将discountFactor判断语句放在了提炼方法中,所以可以去掉。
double getPrice() {
	return basePrice() * discountFactor();
}

如果一开始的时候,没有将basePrice替换成查询式,那么很有可能就不会发现最后会形成这么简洁的代码。

当程序很大时,每一次这样提炼都会有很大的作用。

6.5 引入解释性变量(Introduce Explaining Variable)

你有一个复杂的表达式。

将该表达式(或其中一部分)放入一个临时变量,用变量名来解释该表达式的含义和用途。

if((platform.toUpperCase().indexOf("MAC")>-1)
  	&& (browser.toUpperCase().indexOf("IE")>-1)
  	&& wasInitialized() && resize > 0){
    ...
}

>>>
final boolean isMacOs = platform.toUpperCase().indexOf("MAC")>-1;
final boolean isIEBrowser = browser.toUpperCase().indexOf("IE")>-1;
final boolean wasResized = resize>0;

if(isMacOs && isIEBrowser && wasInitialized() && wasResized){
    ...
}

动机

不影响重构的前提下,加强代码的可读性。

临时变量能够说明一大串表达式的结果意图。

使用这种重构方法的一种情况是:在较长算法中,可以运用临时变量来解释每一步运算的意义。

它与提炼函数(6.1 Extract Method)不冲突——在容易提炼时更偏向于使用提炼函数,用方法名表示含义;在提炼函数很困难的前提下,如果想要让代码更易于阅读,可以使用这种重构方式。

方法的好处在于,它能够在一个类中被使用,也可以让别人通过类的实例化来调用此方法;而临时变量只能用于这个方法。这正是此重构的一个弊端。

做法

  1. 声明一个final临时变量,拆出复杂表达式中的一部分运算进行赋值。

  2. 将表达式中被拆出的那一部分用临时变量替换。

如果那一部分在表达式中重复出现,那真是太好了,体现了临时变量的复用性。

  1. 编译、测试。

范例

double price() {
	return quantity * itemPrice
		- Math.max(0, quantity - 500) * itemPrice * 0.05
		+ Math.min(quantity * itemPrice * 0.1, 100.0);
}

>>> 依次简化这个长长的表达式,因为它实在是太难看明白了
double price() {
	// 底价=数量*单价
	final double basePrice = quantity * itemPrice;
	return basePrice
		- Math.max(0, quantity - 500) * itemPrice * 0.05
		+ Math.min(basePrice * 0.1, 100.0);
}

>>> 目前为止我们将表达式中出现的quantity * itemPrice部分都已提取,换成变量basePrice
> 再提炼其他部分
double price() {
	// 底价=数量*单价
	final double basePrice = quantity * itemPrice;
	// 批发折扣
	final double quantityDiscount = Math.max(0, quantity - 500) * itemPrice
			* 0.05;
	// 运费
	final double shipping = Math.min(basePrice * 0.1, 100.0);
	return basePrice - quantityDiscount + shipping;
}

但是,胆大心细的小伙伴可能会说:这里用提炼函数更好!

的确,这里只是一个例子。

下面是用提炼函数的方法:

double price() {
	return basePrice() - quantityDiscount() + shipping();
}
int basePrice() {
	return quantity * itemPrice;
}
double quantityDiscount() {
	return Math.max(0, quantity - 500) * itemPrice * 0.05;
}
double shipping() {
	return Math.min(basePrice() * 0.1, 100.0);
}

说来说去都是一个意思,只是作用域不同,表现形式不同罢了。

如果说,提炼函数的工作量很大时(比如一个代码段中存在有很多局部变量),就应该考虑使用引入解释性变量,然后再考虑下一步的重构。

也许,在引入解释性变量后,可以搞清楚代码的逻辑,此时就可以使用以查询取代临时变量(6.4 Replace Temp with Query)来有选择地去掉一部分解释性变量,这样会让程序更清晰。

解释性变量的另一点好处是,它很好懂,所以在使用以函数对象取代函数(6.8 Replace Method with Method Object)时,它能当一个好字段。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

NewReErWen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值