临时变量与线长要求


10

我一直在阅读Martin Fowler的Refactoring。它通常很棒,但是Fowler的建议之一似乎引起了一些麻烦。

Fowler建议您使用查询替换临时变量,因此,不要这样做:

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

您可以使用辅助方法:

double basePrice() {
    return _quantity * _itemPrice;
}

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

总的来说,我同意,除了我使用临时变量的原因之一是行太长。例如:

$host = 'https://api.twilio.com';
$uri = "$host/2010-04-01/Accounts/$accountSid/Usage/Records/AllTime";
$response = Api::makeRequest($uri);

如果我尝试内联,则该行将超过80个字符。

或者,我最后得到的是代码链,它们本身并不那么容易阅读:

$params = MustacheOptions::build(self::flattenParams($bagcheck->getParams()));

有哪些调和两者的策略?


10
80个字符大约是我的监视器的三分之一。您确定坚持80个字符行仍然值得吗?
jk。


$host$uri例子是那种做作,但-除非主机正在从环境或其他输入读了,我宁愿他们是在同一行,即使它包装或熄灭的边缘。
2013年

5
不必这么教条。本书列出了可以在需要时使用的技术,而不是您每次必须应用的一组规则。关键是使您的代码更易于维护和阅读。如果重构不执行此操作,则不要使用它。
肖恩·麦克索米辛

虽然我认为80个字符的限制有点过大,但类似的限制(100?)是合理的。例如,我经常喜欢在纵向显示器上编程,因此,超长行可能会令人讨厌(至少在常见的情况下)。
Thomas Eding 2013年

Answers:


16

如何
1.存在行长限制,因此您可以查看+了解更多代码。它们仍然有效。
2.强调对盲目约定的判断
3.避免临时变量,除非针对性能进行优化
4.避免在多行语句中使用深缩进对齐。
5.沿着思想边界将长语句分成多行:

// prefer this
var distance = Math.Sqrt(
    Math.Pow(point2.GetX() - point1.GetX(), 2) + // x's
    Math.Pow(point2.GetY() - point1.GetY(), 2)   // y's
);

// over this
var distance = Math.Sqrt(Math.Pow(point2.GetX() -
    point1.GetX(), 2) + Math.Pow(point2.GetY() -
    point1.GetY(), 2)); // not even sure if I typed that correctly.

推理
我的(调试)临时变量问题的主要根源是它们往往是易变的。即,在编写代码时,我将假定它们是一个值,但是如果函数很复杂,则其他一些代码会在中途更改其状态。(或者相反,变量的状态保持不变,但查询结果已更改)。

除非坚持优化性能,否则请考虑坚持使用查询。这样可以将您用来计算该值的所有逻辑都放在一个地方。

您提供的示例(Java和... PHP?)都允许多行语句。如果线路太长,请将其断开。 jQuery源将这一点发挥到了极致。(第一个语句运行到第69行!)我不一定同意,但是除了使用temp vars,还有其他方法可以使代码可读。

一些示例
1. Python的PEP 8样式指南(不是最漂亮的示例)
2. 梨样式指南上的Paul M Jones(道路参数的中间)
3. Oracle行长+包装约定(用于保持80个字符的有用策略)
4. MDN Java实践(强调程序员对惯例的判断)


1
问题的另一部分是,临时变量通常会超过其值。在小范围内,这不是问题,但在较大范围内,是一个大问题。
罗斯·帕特森

8
如果您担心临时文件会被修改,请在其上放置一个const。
Thomas Eding

3

我认为,使用辅助方法而不是临时变量的最佳论据是人类的可读性。如果您作为一个人,在阅读帮助方法链时比在临时变量中遇到更多麻烦,那么我认为您没有理由提取它们。

(如果我错了,请纠正我)


3

我认为您不需要严格遵循80个字符的准则,也不必提取本地临时变量。但是,应该调查长线和当地临时人员,以更好地表达同一想法。基本上,它们表明给定的函数或行太复杂了,我们需要对其进行分解。但是我们需要小心,因为以不好的方式将任务分解成碎片只会使情况变得更加复杂。所以我要将事情分解为可恢复的简单组件。

让我看看您发布的示例。

$host = 'https://api.twilio.com';
$uri = "$host/2010-04-01/Accounts/$accountSid/Usage/Records/AllTime";
$response = Api::makeRequest($uri);

我的观察是,所有twilio api调用都将从“ https://api.twilio.com/2010-04-1/”开始,因此有一个非常明显的可重用函数:

$uri = twilioURL("Accounts/$accountSid/Usage/Records/AllTime")

实际上,我认为生成URL的唯一原因是发出请求,所以我会这样做:

$response = TwilioApi::makeRequest("Accounts/$accountSid/Usage/Records/AllTime")

实际上,许多网址实际上都以“ Accounts / $ accountSid”开头,因此我也可能会提取这些网址:

$response = TwilioApi::makeAccountRequest($accountSid, "Usage/Records/AllTime")

并且,如果我们将twilio api设置为拥有帐号的对象,则可以执行以下操作:

$response = $twilio->makeAccountRequest("Usage/Records/AllTime")

使用$ twilio对象的好处是使单元测试更加容易。我可以给该对象一个不同的$ twilio对象,该对象实际上不会回调到twilio,这将更快并且不会对twilio产生任何奇怪的影响。

让我们看看另一个

$params = MustacheOptions::build(self::flattenParams($bagcheck->getParams()));

在这里,我会考虑:

$params = MustacheOptions::buildFromParams($bagcheck->getParams());

要么

$params = MustacheOptions::build($bagcheck->getFlatParams());

要么

$params = MustacheOptions::build(flatParams($backCheck));

取决于哪个是更可重用的习惯用法。


1

实际上,在一般情况下,我不同意著名的福勒先生。

从以前的内联代码中提取方法的优点是代码重用;该方法中的代码现已从其最初的使用中分离出来,并且现在可以在代码中的其他位置使用,而无需复制和粘贴(如果必须更改复制代码的一般逻辑,则有必要在多个位置进行更改) 。

但是,具有相等,通常更大的概念价值的是“价值重用”。福勒先生称这些提取的方法来代替临时变量“查询”。好吧,更有效率;是否需要多次查询数据库,或者需要查询一次并存储结果(假设该值是足够静态的,以至于您不希望它发生变化)?

对于您示例中相对微不足道的几乎所有计算,在大多数语言中,存储一次计算的结果要比继续进行计算便宜。因此,按需重新计算的一般建议是不合理的;它花费了更多的开发人员时间和更多的CPU时间,并节省了微不足道的内存,在大多数现代系统中,这是这三者中最便宜的资源。

现在,可以将辅助方法与其他代码结合起来变得“懒惰”。首次运行时,它将初始化变量。所有进一步的调用将返回该变量,直到明确告知该方法重新计算为止。这可能是方法的参数,或者是其他代码设置的标志,该标志会更改此方法的计算所依赖的任何值:

double? _basePrice; //not sure if Java has C#'s "nullable" concept
double basePrice(bool forceCalc)
{
   if(forceCalc || !_basePrice.HasValue)
      return _basePrice = _quantity * _itemPrice;
   return _basePrice.Value;
}

现在,对于这种微不足道的计算,执行的工作比保存的还要多,因此,我通常建议您坚持使用temp变量;但是,对于更复杂的计算,通常希望避免多次运行,并且需要在代码中的多个位置进行计算,这是您要这样做的方式。


1

辅助方法占有一席之地,但是在确保数据一致性和不必要地增加变量范围时,必须要小心。

例如,您自己的示例引用:

double getPrice() {
    final double discountFactor;
    if (basePrice() > 1000) discountFactor = 0.95;      <--- first call
    else discountFactor = 0.98;
    return basePrice() * discountFactor;                <--- second call
}

显然,_quantity_itemPrice都是全局变量(或至少是类级别),因此有可能在外部进行修改getPrice()

因此,第一次调用有可能basePrice()返回与第二次调用不同的值!

因此,我建议助手功能对于隔离复杂的数学很有用,但是作为局部变量的替代品,您需要小心。


您还必须避免荒谬的还原 -是否应将计算discountFactor拆分为方法?因此,您的示例变为:

double getPrice()
{
    final double basePrice      = calculateBasePrice();
    final double discountFactor = calculateDiscount( basePrice );

    return basePrice * discountFactor;
}

分区超过一定级别实际上会使代码的可读性降低。


+1,使代码的可读性降低。过度分区会隐藏源代码试图解决的业务问题。在某些特殊情况下,在getPrice()中应用了优惠券,但是如果该优惠券隐藏在函数调用链的深处,则业务规则也会被隐藏。
Reactgular

0

如果您碰巧使用具有命名参数(ObjectiveC,Python,Ruby等)的语言进行工作,则temp vars的用处不大。

但是,在您的basePrice示例中,查询可能需要一段时间才能执行,并且您可能希望将结果存储在temp变量中以备将来使用。

但是,像您一样,出于清楚和行长考虑,我使用临时变量。

我还看到程序员在PHP中执行以下操作。它很有趣,而且调试起来很棒,但是有点奇怪。

$rs = DB::query( $query = "SELECT * FROM table" );
if (DEBUG) echo $query;
// do something with $rs

0

此建议的基本原理是您希望能够在应用程序的其他位置使用相同的预计算。请参阅重构模式目录中的用查询替换温度

然后可以将新方法用于其他方法

    double basePrice = _quantity * _itemPrice;
    if (basePrice > 1000)
        return basePrice * 0.95;
    else
        return basePrice * 0.98;

           http://i.stack.imgur.com/mKbQM.gif

    if (basePrice() > 1000)
        return basePrice() * 0.95;
    else
        return basePrice() * 0.98;
...
double basePrice() {
    return _quantity * _itemPrice;
}

因此,在您的主机和URI示例中,仅当我计划重用相同的URI或主机定义时,才应用此建议。

如果是这种情况,由于命名空间的原因,我将不定义全局uri()或host()方法,而是定义一个包含更多信息的名称,例如twilio_host()或archive_request_uri()。

然后,对于行长问题,我看到几个选项:

  • 创建一个局部变量,例如uri = archive_request_uri()

原理:在当前方法中,您希望URI是提到的URI。URI定义仍被分解。

  • 定义本地方法,例如 uri() { return archive_request_uri() }

如果您经常使用Fowler的建议,您会知道uri()方法是相同的模式。

如果由于语言选择,您需要使用“ self。”访问本地方法,则建议您使用第一种解决方案,以提高表达能力(在Python中,我将在当前方法内部定义uri函数)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.