18

通常,避免 GOTO 是一种很好的做法。牢记这一点,我一直在与一位同事就这个话题进行辩论。

考虑以下代码:

Line:
    while( <> ) {
        next Line if (insert logic);
    }

使用循环标签算作 goto 吗?

这是perldoc 中的perlsyn必须说的:

以下是 C 程序员如何在 Perl 中编写特定算法的方法:

for (my $i = 0; $i < @ary1; $i++) {
    for (my $j = 0; $j < @ary2; $j++) {
        if ($ary1[$i] > $ary2[$j]) {
            last; # can't go to outer :-(
        }
        $ary1[$i] += $ary2[$j];
    }
    # this is where that last takes me
}

而下面是一个对这个习惯用法更熟悉的 Perl 程序员可能会这样做的方式:

OUTER: for my $wid (@ary1) {
    INNER:   for my $jet (@ary2) {
                 next OUTER if $wid > $jet;
                 $wid += $jet;
             }
       }

我对此的看法是否定的,因为您明确告诉循环短路并前进,但是我的同事不同意,说这只是一个花哨的 GOTO,应该避免。我正在寻找一个令人信服的论点或文档来解释为什么这是或不是 GOTO。我也会接受解释为什么这在 perl 中被认为是或不被认为是好的做法。

4

11 回答 11

29

Dijkstras 的意图绝不是任何类似于 goto 的东西都被认为是有害的。正是由于 goto 被用作几乎任何类型的程序流程更改的主要结构的代码结构,都将导致我们今天所说的意大利面条代码。

您应该阅读原始文章并记住它是在 1968 年编写的,当时标记跳转是几乎所有编程语言中的主要流程控制结构。

https://www.cs.utexas.edu/users/EWD/ewd02xx/EWD215.PDF

于 2010-06-21T14:07:02.927 回答
16

GOTO 标签的危险在于它们会创建意大利面条式代码并使逻辑不可读。在这种情况下,这些都不会发生。使用 GOTO 语句有很多有效性,大部分辩护来自 Donald Knuth [文章]。

深入研究您的 C 和 Perl 示例之间的差异...如果您考虑 C 程序在汇编级别发生的情况,无论如何它都会编译为 GOTO。如果您做过任何 MIPS 或其他汇编编程,那么您会发现这些语言中的大多数都没有任何循环结构,只有条件分支和无条件分支。

最后,它归结为可读性和可理解性。通过保持一致,这两者都得到了极大的帮助。如果您的公司有风格指南,请遵循它,否则遵循 perl 风格指南对我来说是个好主意。这样,当其他 perl 开发人员将来加入您的团队时,他们将能够开始工作并对您的代码库感到满意。

于 2010-06-21T14:12:29.783 回答
5

只要它使代码更易于理解,谁在乎它是否算作 goto?使用 goto 通常比在 if() 和循环条件中进行大量额外测试更具可读性。

于 2010-06-21T14:07:13.247 回答
3

IMO,您的代码比较不公平。目标是可读的代码。

公平地说,您应该将惯用的带标签的 Perl 嵌套循环与没有标签的循环进行比较。C 风格的 for 和阻塞 if 语句增加了噪音,使得无法比较这些方法。

标签:

OUTER: for my $wid (@ary1) {
    INNER:   for my $jet (@ary2) {
                 next OUTER if $wid > $jet;
                 $wid += $jet;
             }
       }

无标签:

for my $wid (@ary1) {
   for my $jet (@ary2) {
       last if $wid > $jet;
       $wid += $jet;
   }
}

我更喜欢带标签的版本,因为它明确了条件的效果$wid > $jet。如果没有标签,您需要记住它last在内部循环上运行,并且当内部循环完成时,我们将移动到外部循环中的下一个项目。这不完全是火箭科学,但它是真实的、可证明的、认知开销。正确使用,标签使代码更具可读性。

更新:

stocherilac询问如果嵌套循环之后有代码会发生什么。这取决于您是要根据内部条件跳过它还是始终执行它。

如果您想跳过外循环中的代码,则标记的代码将按需要工作。

如果要确保每次都执行它,可以使用continue块。

OUTER: for my $wid (@ary1) {
    INNER:   for my $jet (@ary2) {
                 next OUTER if $wid > $jet;
                 $wid += $jet;
             }
       }
       continue {
           # This code will execute when next OUTER is called.
       }
于 2010-06-21T18:40:51.537 回答
2

我认为这种区别有些模糊,但这是goto perldoc关于(皱眉)goto 语句的陈述:

goto-LABEL 表单找到标有 LABEL 的语句并在那里恢复执行。

...

Perl 的作者从未觉得需要使用这种形式的 goto(在 Perl 中,也就是说;C 是另一回事)。(不同之处在于 C 不提供与循环控制相结合的命名循环。Perl 提供,这取代了其他语言中 goto 的大多数结构化用法。)

然而perlsyn perldoc是这样说的:

只要表达式为真,while 语句就会执行块。只要表达式为假,直到语句执行块。LABEL 是可选的,如果存在,则由一个标识符和一个冒号组成。LABEL 标识循环控制语句 next、last 和 redo 的循环。如果省略 LABEL,则循环控制语句引用最内层的封闭循环。这可能包括在运行时动态回顾调用堆栈以找到 LABEL。如果您使用 use warnings pragma 或 -w 标志,这种绝望的行为会触发警告。

绝望的行为位对我来说看起来不太好,但我可能误解了它的含义。

学习 Perl书(第5 版,第 162 页)这样说:

当您需要使用不是最里面的循环块时,请使用标签。

...

请注意,标签命名了整个块;它没有在代码中标记目标点。[毕竟这不是goto 。]

这有助于澄清事情吗?可能不是... :-)

于 2010-06-21T14:47:01.937 回答
2

breakPerl 中带标签的循环跳转与C 的 GOTO 一样多continue

于 2010-06-21T14:47:51.110 回答
2

我会这样回答,我不确定这是否与其他人所说的有很大不同:

因为您只能在当前范围内移动或移动到父范围内,所以它们的危险性远低于通常暗示的危险goto,请注意:

if (1) {
  goto BAR;
  die 'bar'
}
BAR:

这应该很明显,但这不会(不能朝这个方向移动)。

if (0) {
  BAR:
  die 'bar'
}
goto BAR;

许多用例labels与 goto 的不同之处在于它们只是核心流控制的更明确的变体。要声明它们绝对更糟,那就是暗示:

LOOP: while (1) {
  next LOOP if /foo;
}

在某种程度上比

while (1) {
  next if /foo/;
}

如果排除样式,这简直是不合逻辑的。但是,说到风格,后一种变体更容易阅读——它确实让你不必查找正确命名的标签。读者知道更多next(您正在当前范围内重新启动循环),这更好。

让我们看另一个例子

while (1) {

  while (1) {
    last;
  }

  stuff;

}

-对-

FOO: while (1) {

  BAR: while (1) {
    next FOO;
  }

  stuff;

}

在这里的后一个示例next FOO中,跳过stuff——您可能希望这样做,但这是个坏主意。这意味着程序员已经完成了父范围的读取,这是一个最好避免的假设。总之,label没有那么糟糕goto,有时它们可​​以简化代码;但是,在大多数情况下,它们应该被避免。label当我在 CPAN 上遇到不带 s 的循环时,我通常会重写它们。

于 2010-06-21T16:51:17.227 回答
1

gotos不好,因为它们创建了难以理解的代码——尤其是通常被称为“意大利面条代码”的代码。有什么难理解的next Line...??

您可以将其称为循环“名称”,它确实有助于强调循环边界。您不会跳入与循环相关的任意点;你将回到循环的顶部。

可悲的是,如果它是一个团体或房屋标准,可能没有什么可以说服该团体这不是一个 goto。我有一位经理绝对坚持三元运算符使事情变得难以阅读,并且更喜欢我对所有事情都使用 if 块。我有一个很好的论点,任何事情都可以在 if-else 的子句中完成,但是三元组明确表明您正在寻找一个特定的值。没有销售。

于 2010-06-21T15:39:53.600 回答
1

这种跳转是对类似 goto 语句的严格使用。所以它肯定比随意使用 goto 的危害要小。(正如 kasperjj 所写,“ Dijkstras 的意图绝不是任何类似于 goto 的东西都被认为是有害的。”

IMO,这种 Perl 类型的跳转甚至比 C 的“break”和“continue”设计得更好,因为它清楚地表明了我们中断或继续什么循环,并且在代码更改时更稳固。(此外,它还允许中断或继续外循环。)

有些专家不喜欢 break/continue 之类的东西,但在某些时候,需要在经验法则和可读性之间做出权衡,精心选择的 break/continue 甚至 goto 可能比“政治上的”更具可读性正确”的代码。

于 2010-06-21T16:29:21.610 回答
1

break/last 和 continue/next 是 gotos。我不明白为什么有人会竭尽全力避免使用关键字但使用不同的关键字来做同样的事情......

于 2010-06-21T16:30:02.760 回答
1

4.4.4. 回路控制

我们提到您可以在循环上放置一个 LABEL 来为其命名。循环的 LABEL 标识循环控制运算符 next、last 和 redo 的循环。LABEL 将循环命名为一个整体,而不仅仅是循环的顶部。因此,引用循环的循环控制运算符实际上并不“转到”循环标签本身。就计算机而言,标签可以很容易地放在循环的末尾。但出于某种原因,人们喜欢贴在顶部的东西。

编程 Perl

于 2010-12-23T14:09:18.990 回答