用查询替换Temp

时间:2008-11-26 14:48:52

标签: java refactoring

现在推荐使用查询重构方法替换Temp,但是 似乎非常低效,收益很少。

来自Martin Fowler网站的方法给出了以下示例:

将表达式提取到方法中。用表达式替换对temp的所有引用。然后可以在其他方法中使用新方法。

    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;
} 

为什么这是个好主意?这肯定意味着计算不必要地重复,你有调用函数的开销。我知道CPU周期 很便宜但扔掉它们似乎不小心?

我错过了什么吗?

8 个答案:

答案 0 :(得分:6)

阅读过Fowler的webpage on it之后,我认为这样做没有任何好处。唯一可能的收获是通过将可能经常使用的表达式隔离到一个点,但最好通过以下方式处理:

    double basePrice = basePrice();
    if (basePrice > 1000)
            return basePrice * 0.95;
    else
            return basePrice * 0.98;
除了阅读他的书之外,福勒没有解释为什么他修改后的代码比原版更好。

答案 1 :(得分:4)

我认为这种重构的大部分效用并不是来自它自身的应用,而是作为向极端常见和有用的提取方法迈出的一步。在局部变量阻碍Extract方法或通过其他参数使其复杂化的情况下,'用查询替换temp有助于'使Extract Method更容易。同样在这些情况下,生成的查询方法由两种不同的方法重用,这两种方法是提取方法的结果。

答案 2 :(得分:4)

这是一个重要的重构,因为它是对单一责任的重构,它是DRY失败的解决方案!

temps的主要问题(特别是使用长方法的天真代码,长达数百行!)是它们是可变的本地状态。明显的风险(正如福勒所讨论的那样?)是有人可能会在漫长的方法中间做出改变,并在最后打破一些东西。 (已经看到了这笔费用)

没有测试,这种方法有无数的依赖 - 多么糟糕! :)

删除temp是关于重构单一责任。

示例 - 今天我发现了一个错误,它涉及将错误的临时变量传递给服务。如果我删除temps(有几个,所有字符串),那个bug(疏忽)就不会发生。

我的方法持有临时变量的原因是它正在做它不应该工作的......并且这个逻辑会被类似的类重复......它是否一致?没有!

通过删除temp,我还会将代码重构为具有适当职责的类,通过一些简单的测试可以覆盖110%。

没有巨大的夹具来测试一些微不足道的东西

如果我称之为昂贵的东西,我会将结果作为值对象/聚合返回。可能是'临时代码'应该由聚合内化。

所以删除临时推动你进入集中(单一)责任 - >固体!

答案 3 :(得分:4)

我现在正在重读“重构”,其中一个原因是Extract Method第116页末尾的以下引用。

背景:他描述如果要提取的代码使用局部变量,则Extract Method很难。

  

临时变量通常非常丰富,以至于它们使提取变得非常尴尬。在这些情况下,我尝试使用Replace Temp with Query(120)来减少临时值。 ...

因此,一次重构可能取决于首先进行另一次重构,而这种重构本身并不明显。另一个例子是在替换Inline Method之前Method with Method Object

答案 4 :(得分:3)

它旨在更多地揭示代码的意图。在某些情况下,它可能被滥用但不太可能。例如,您可以使用查询更新查询以获得5%和2%的折扣,但是您可以在折扣的名称原因中描述方法的名称。记住,今天可能是显而易见的,但在6个月内可能不是这样 - 正如我所说 - 如果我忘记这不是问题,但是当我忘记时。

  if (basePrice() > 1000)
     return bigTicketDiscount()
  else
     return regularDiscount()

double bigTicketDiscount(){
  return basePrice() * 0.95;
}

double regularDiscount(){
  return basePrice() * 0.98
}

答案 5 :(得分:3)

还有更多关于在重构书中使用查询替换温度的内容,它是:

  

在这种情况下,您可能会担心表现。与其他性能问题一样,让它   暂时滑动。十分之九,没关系。什么时候重要,你会修复   优化期间的问题。通过更好地考虑代码,您将经常发现更强大的功能   优化,你可能会错过没有重构。如果情况变得更糟,那就是   很容易把温度回来。

答案 6 :(得分:1)

您(原始问问者和James Curran)都缺少在此特定示例中的计算方法。该代码将从if的一个分支中下来,然后调用basePrice(),以执行计算。或者,代码在if的另一个分支下移,并且也在那里进行计算。

无论如何,乘法都会完成,但仍然只有一次。就像临时版本一样。

是的,很高兴知道默认情况下不缓存查询,而temp本质上是一种缓存形式。在此特定示例中,区分无关紧要。缓存(例如临时文件)的优势在于,它可以节省CPU周期(在这种情况下,实际上不是这样,就乘法本身而言,就调用,堆栈跳转等而言,然后确定……取决于编译器)。缓存的缺点是,如果您草率的话,它可能会使您过时或出现错误的数据。这是Fowler提到的特定临时缺点(它们在时间和词汇上都是局部的)。

我通常要做的是,大部分时间我都在查询中编码,并且当我看到我多次执行特定查询时(不同于本示例,它在单独的条件分支中),如果需要在本地使用临时文件,则可以使用临时文件;如果需要在更广的本地范围内访问临时文件,则可以引入备忘录或其他类型的缓存系统(例如简单的全局变量或类成员)。

那时,与这种重构相反的操作“用Temp替换查询”就派上用场了。

答案 7 :(得分:0)

在我看来,通过这样做,你有助于为你的原始方法做出一个单一的目的;它不再负责计算basePrice和折扣价格(或者它正在计算的任何东西)。这是牺牲一些CPU周期以使代码更易于维护的示例。 “浪费”的cpu周期可能不是你会注意到的,并且编译器也可以优化它(可能通过注意方法相对简单而不是调用方法它插入实际代码(更像是无论如何,这些类型的优化最好留给机器,让人们可以使用更清晰的代码。