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

tech2024-11-28  11

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

接着上一篇往下讲。 做法

简单情况的步骤:

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

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

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

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

编译。

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

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

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

编译,测试。

在该临时变量身上实施内联临时变量(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)不冲突——在容易提炼时更偏向于使用提炼函数,用方法名表示含义;在提炼函数很困难的前提下,如果想要让代码更易于阅读,可以使用这种重构方式。

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

做法

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

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

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

编译、测试。

范例

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)