这个重构技巧指,如果你有一个临时变量用来保存一个表达式的运算结果,那么就将这个表达式提炼到一个独立函数中,再将这个临时变量的所有引用点改为对新函数的调用。
同样是一个消除临时变量的一个重构方法。我认为是Inline Temp的一个高级应用。因为Inline Temp只是把临时变量内联化了,但是如果临时变量所保存的是一个复杂的计算式,那么利用Extract Method方法后,就是本重构方法的思想了。
例1:
double basePrice = _quantity * _itemPrice;
if (basePrice > 1000)
return basePrice * 0.95;
else
return basePrice * 0.98;
这段代码已经很简洁了,它使用一个临时变量“basePrice”保存了一个计算式的结果,并在后续代码中使用。重构后的代码如下:
if (basePrice() > 1000)
return basePrice() * 0.95;
else
return basePrice() * 0.98;
...
double basePrice() {
return _quantity * _itemPrice;
}
这样重构后,临时变量就取消了,但是同时多出一个函数,那么其好处是:函数是可重用的,其它函数中均可再调用,而临时变量是只针对某个函数内部。以后只要单独维护basePrice函数就可以影响所有的调用者。调用的地方,代码的可读性也变得相当好。
但是这种重构,也一定能看出是以代码性能作为代价的。没错,basePrice的计算式总是要被调用两次。我的看法是,任何事情都是鱼和熊掌不可兼得。代码是让机器跑的,高性能的代码必然直接干脆从而晦涩难读;代码是让人阅读让人写的,所以良好的设计、清晰的结构,也必然会降低性能。所以我们可以在项目的定位上来决定,把我们的代码对机器的高效运行与对人的高效阅读做一个合理的调节。不过如果你还不能很好的确定的话,那么一个建议是:先重构,再做优化。因为重构是理清代码思路,也是优化的前提,代码思路都不清楚,优化又从何谈起呢?所有重构时,就专注于重构,先不管优化的问题,如果对性能真有影响,那么可以在优化时期解决。
再来看一个很经典的例子:
例2:
double getPrice() {
int basePrice = _quantity * _itemPrice;
double discountFactor;
if (basePrice > 1000)
discountFactor = 0.95;
else
discountFactor = 0.98;
return basePrice * discountFactor;
}
这段代码有两个临时变量,分别用来保存两种表达式的结果,我们分步来重构:
重构1:
double getPrice() {
double discountFactor;
if (basePrice() > 1000)
discountFactor = 0.95;
else
discountFactor = 0.98;
return basePrice() * discountFactor;
}
private int basePrice() {
return _quantity * _itemPrice;
}
重构2:
double getPrice() {
return basePrice() * discountFactor();
}
private int basePrice() {
return _quantity * _itemPrice;
}
private double discountFactor() {
if (basePrice() > 1000)
return = 0.95;
else
return = 0.98;
}
重构后的代码逻辑非法清楚,各自函数也都干了各自最本分的事情。
官方解释:
Replace Temp with Query
double basePrice = _quantity * _itemPrice; if (basePrice > 1000) return basePrice * 0.95; else return basePrice * 0.98;
if (basePrice() > 1000) return basePrice() * 0.95; else return basePrice() * 0.98; ... double basePrice() { return _quantity * _itemPrice; }
For more information see page 120 of Refactoring
Additional Comments
Side Effects
Paul Haahr pointed out that you can't do this refactoring if the code in between the the assignment to the temp and the use of the temp changes the value of the expression that calculates the temp. In these cases the code is using the temp to snapshot the value of the temp when it's assigned. The name of the temp should convey this fact (and you should change the name if it doesn't).
He also pointed out that it is easy to forget that creating a reference object is a side effect, while creating a value object isn't.