企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
### Replace Temp with Query(以查询取代临时变量) 你的程序以一个临时变量(temp)保存某一表达式的运算结果。 将这个表达式提炼到一个独立函数(译注:所谓查询式,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; } ~~~ **动机(Motivation)** 临时变量的问题在于:它们是暂时的,而且只能在所属函数内使用。由于临时变量只有在所属函数内才可见,所以它们会驱使你写出更长的函数,因为只有这样你才能访问到想要访问的临时变量。如果把临时变量替换为一个查询式(query method),那么同一个class中的所有函数都将可以获得这份信息。这将带给你极大帮助,使你能够为这个编写更清晰的代码。 Replace Temp with Query往往是你运用Extract Method 之前必不可少的一个步骤。局部变量会使代码难以被提炼,所以你应该尽可能把它们替换为查询式。 这个重构手法较为直率的情况就是:临时变量只被赋值一次,或者赋值给临时变量的表达式不受其他条件影响。其他情况比较棘手,但也有可能发生。你可能需要先运用 Split Temporary Variable 或Separate Query from Modifier 使情况变得简单一些,然后再替换临时变量。如果你想替换的临时变量是用来收集结果的(例如循环中的累加值),你就需要将某些程序逻辑(例如循环)拷贝到查询式(query method)去。 **作法(Mechanics)** 首先是简单情况: - 找出只被赋值一次的临时变量。 - 如果某个临时变量被赋值超过一次,考虑使用Split Temporary Variable 将它分割成多个变量。 - 将该临时变量声明为final。 - 编译。 - 这可确保该临时变量的确只被赋值一次。 - 将「对该临时变量赋值」之语句的等号右侧部分提炼到一个独立函数中。 - 首先将函数声明为private。日后你可能会发现有更多class需要使用 它,彼时你可轻易放松对它的保护。 - 确保提炼出来的函数无任何连带影响(副作用),也就是说该函数并不修改任何对象内容。如果它有连带影响,就对它进行Separate Query from Modifier。 - 编译,测试。 - 在该临时变量身上实施Replace Temp with Query。 我们常常使用临时变量保存循环中的累加信息。在这种情况下,整个循环都可以被提为一个独立函数,这也使原本的函数可以少掉几行扰人的循环码。有时候,你可能会用单一循环累加好几个值,就像本书p.26的例子那样。这种情况下你应该针对每个累加值重复一遍循环,这样就可以将所有临时变量都替换为查询式(query)。当然,循环应该很简单,复制这些代码时才不会带来危险。 运用此手法,你可能会担心性能问题。和其他性能问题一样,我们现在不管它,因 为它十有八九根本不会造成任何影响。如果性能真的出了问题,你也可以在优化时期解决它。如果代码组织良好,那么你往往能够发现更有效的优化方案;如果你没有进行重构,好的优化方案就可能与你失之交臂。如果性能实在太糟糕,要把临时变量放回去也是很容易的。 **范例(Example)** 首先,我从一个简单函数开始: ~~~ 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; final double discountFactor; if (basePrice > 1000) discountFactor = 0.95; else discountFactor = 0.98; return basePrice * discountFactor; } ~~~ 这样一来,如果有任何问题,编译器就会警告我。之所以先做这件事,因为如果临时变量不只被赋值一次,我就不该进行该项重构。接下来我开始替换临时变量,每次一个。首先我把赋值(assignment)动作的右侧表达式提炼出来: ~~~ double getPrice() { final int basePrice = basePrice(); final double discountFactor; if (basePrice > 1000) discountFactor = 0.95; else discountFactor = 0.98; return basePrice * discountFactor; } private int basePrice() { return _quantity * _itemPrice; } ~~~ 编译并测试,然后开始使用Replace Temp with Query。首先把临时变量basePrice的第一个引用点替换掉: ~~~ double getPrice() { final int basePrice = basePrice(); final double discountFactor; if (basePrice() > 1000) discountFactor = 0.95; else discountFactor = 0.98; return basePrice * discountFactor; } ~~~ 编译、测试、下一个(听起来像在指挥人们跳乡村舞蹈一样)。由于「下一个」已经是basePrice的最后一个引用点,所以我把basePrice临时变量的声明式一并摘除: ~~~ double getPrice() { final double discountFactor; if (basePrice() > 1000) discountFactor = 0.95; else discountFactor = 0.98; return basePrice() * discountFactor; } ~~~ 搞定basePrice之后,我再以类似办法提炼出一个discountFactor(): ~~~ double getPrice() { final double discountFactor = discountFactor(); return basePrice() * discountFactor; } private double discountFactor() { if (basePrice() > 1000) return 0.95; else return 0.98; } ~~~ 你看,如果我没有把临时变量basePrice替换为一个查询式,将多么难以提炼discountFactor()! 最终,getPrice()变成了这样: ~~~ double getPrice() { return basePrice() * discountFactor(); } ~~~