10

最近很多人指责我只提到一个词——“goto”。
这让我想知道,为什么它被认为是一个如此讨厌的词。
我知道之前关于该主题的几次讨论,但这并不能说服我 -有些答案只是说“这很糟糕”,甚至没有试图解释,有些还带来了与 PHP、IMO 等脚本语言无关的原因

无论如何,我要问一个非常特别的问题:
让我们比较gotothrow陈述。
在我看来,两者都做同样的事情:避免基于某些条件执行某些代码部分。
如果是这样 - 是否throw有与 goto 相同的缺点?(如果有的话)?
如果不是 - 那就是区别。

任何有足够经验的人,谁能说出以下两个代码片段的代码结构之间的基本概念差异?
关于这些非常代码片段,而不是“理论上”或“一般”或“这是一个不错的 XKCD 漫画!”。
为什么第一个被认为是聪明的,而后一个被认为是最致命的罪恶?

#$a=1;
#$b=2;

/* SNIPPET #1 */

try {
    if (!isset($a)) {
      throw new Exception('$a is not set');
    }
    if (!isset($b)) {
      throw new Exception('$b is not set');
    }
    echo '$a + $b = '.($a + $b)."<br>\n";
} catch (Exception $e) {
    echo 'Caught exception: ', $e->getMessage(), "<br>\n";
}

/* SNIPPET #2 */

if (!isset($a)) { 
  $message = '$a is not set';
  goto end;
}
if (!isset($b)) {
  $message = '$b is not set';
  goto end;
}
echo '$a + $b = '.($a + $b)."<br>\n";

end:
if (!empty($message)) {
  echo 'Caught exception: ', $message, "<br>\n";
}

请注意,我知道 throw在制作意大利面条时更加强大和灵活。这对我来说没有主要区别。这是使用问题,而不是概念问题。

编辑
很多人告诉我,第一个示例应该更新使用。
原因:异常应该只用于处理错误,而不是实现业务逻辑。
看起来很明智。

因此,停止无用代码执行的唯一方法是goto(更不用说一些替代品,例如while,return等,它们是相同的,但更难实现)?

4

11 回答 11

13

Throw/catch 比 goto 灵活得多:您可以从调用抛出异常的代码的函数中捕获。此外,自动调用所需的析构函数。

于 2010-07-25T13:59:12.560 回答
7

我认为你需要反过来问这个问题:goto当异常完全适合这种情况时,你为什么要使用?这就是例外的情况

您不妨问“为什么要使用whilefor以及我可以轻松使用的功能goto?”

于 2010-07-25T13:56:55.740 回答
5

goto将执行路径硬编码到代码中。另一方面,异常允许在运行时确定执行路径。

例如,假设您有一个在出错时引发异常的数据库类。除了例外,您可以捕获该错误,并在呈现错误页面之前执行其他操作(例如清理分配的资源,或者如果您使用非事务性数据库类型,则“回滚”先前的更改。如果您使用 goto,您不会没有机会这样做,因为 goto 会呈现错误页面。

请记住,保持您的代码可重用和灵活。 goto是两者的对立面...

于 2010-07-25T14:03:03.373 回答
5

goto在您的示例中,实际上没有区别,因为您正在检查错误条件并在条件失败时引发异常或调用。您的示例可以重新编码以消除对一构造的需要。

异常有用的地方您正在调用可能具有错误状态但无法自行处理的方法,如下面的伪代码所示:

try
{
    $c = add($a, $b);
    echo '$a + $b = '.($c)."<br>\n";
}
catch (Exception $e)
{
    echo 'Caught exception: ', $e->getMessage(), "<br>\n";
}

add方法进行错误检查:

if (!isset($a))
{
    throw new Exception('$a is not set');
}
if (!isset($b))
{
    throw new Exception('$b is not set');
}

然后返回加法的结果。

这意味着您的主要代码流显示了程序的预期路径。

于 2010-07-25T14:03:08.790 回答
3

由于还没有人提供 OP 认为可以接受的这个问题的答案,所以我会把我的帽子扔在戒指上。

…以下两个代码片段的代码结构之间的基本概念差异?

您提供的两个代码片段之间在概念上完全没有区别。在这两种情况下,都会测试一个条件,如果满足,则将一条消息抛出到程序的另一部分进行处理。

为什么第一个被认为是聪明的,而后一个被认为是最致命的罪恶?

感谢 Shrapnel 上校为接下来的咆哮打开了大门。

<咆哮>

每个编程结构都有被滥用的可能性。教条式的迫害goto让我想起了对 PHP 的类似抨击。PHP 会导致不良的编程习惯。比较:Goto 导致意大利面条代码。

简单地说:goto是“邪恶的”,因为 Edsger Dijkstra 这么说,而 Niklaus Wirth 给了它他的认可印章。;-p

</rant>

为了您的阅读乐趣:http ://en.wikipedia.org/wiki/Goto

对 GOTO 最著名的批评可能是 Edsger Dijkstra 1968 年的一封信,名为Go To Statement Considered Harmful。在那封信中,Dijkstra 认为不受限制的 GOTO 语句应该从高级语言中废除,因为它们使分析和验证程序正确性的任务变得复杂(尤其是那些涉及循环的)。Donald Knuth 的带有 go to 语句的结构化编程中提出了另一种观点,它分析了许多常见的编程任务,并发现在其中一些任务中,GOTO 是最适合使用的语言结构。

它接着说,

一些程序员,如 Linux 内核设计师和编码员 Linus Torvalds 或软件工程师和书籍作者 Steve McConnell 也反对 Dijkstra 的观点,指出 GOTO 可以是一种有用的语言特性,提高程序速度、大小和代码清晰度,但只有在由相当明智的程序员以明智的方式使用。

也就是说,自 Commodore BASIC 以来,我个人还没有找到 GOTO 的实际用途,但这既不是这里也不是那里。:-)

于 2011-10-21T16:39:33.560 回答
3

goto是否适合做这样的事情:

foreach ($items as $item) {
    if ($item->is_match()) goto match;
}

$item = new Item();

match:
render('find_item.php', $item);

与此相反:

$matching_item = null;
foreach ($items as $item) {
    if ($item->is_match()) {
        $matching_item = $item;
        break;
    }
}
if ($matching_item === null) {
    $item = new Item();
}

render('find_item.php', $item);
于 2010-10-14T14:06:35.870 回答
2

“错误”的是goto它可以实现多种控制机制,而不仅仅是throw,因此几乎没有固有结构。goto正因为如此,脚本程序员对使用替代方案感兴趣有几个原因:

首先,我们开始看到脚本从纯解释型转变为编译型字节码。这方面的好例子可以在任何地方找到,例如 Python、Perl 6、Scala (Lift)、Clojure (Compojure)。甚至 PHP 也有字节码编译器。由于goto结构很少,无法编译控制机制,这是相当大的性能损失,因为现代编译器通常非常擅长这一点。对于throw,编译器可以开始假设抛出的异常并不常见,因此它通常会以不太优化的“异常”控制路径为代价来优化“正常”控制路径。

即使您没有完整的编译器,当程序员使用具有更多结构的控制机制时,解释器也可以为程序员提供更多帮助。例如,for循环块由编译器/解释器在词法范围内限定,因此程序员可以使用此范围来模块化代码块。否则,goto您将被迫污染变量命名空间。

此外,如果您的语言允许您声明throw异常方法,编译器/解释器会通知您,您尚未处理您调用的方法可能遇到的所有可能异常(类似于throwsJava,如果您有经验的话)。使用goto,编译器不知道您要做什么,因此在编译和测试期间它不会帮助您。

Goto 还要求程序员从错误中恢复。想象一下,如果你有一些像这样的代码(从你的片段中抓取和编辑):

if (!isset($a)) { 
  $message = '$a is not set';
  goto errorA;
}
if (!isset($b)) {
  $message = '$b is not set';
  goto errorB;
}
if (!moonInRightPhase) {
   goto errorP
}
echo '$a + $b = '.($a + $b)."<br>\n";

errorA:
// Do something non-trivial and unique to handle $a not being set.
goto afterError

errorP:
// Do something non-trivial and unique to handle moon phase
// This error requires drastic failure of code path:
goto failure

errorB:
// Do something non-trivial and unique to handle $b not being set.
goto afterError

afterError:
// Program continues

return SUCCESS

failure:
return FAILURE

这里的问题是程序员需要做正确的事情。如果他忘记了goto afterError,他将运行下一个异常路径。有了throw这些机制,就不容易出错。

最后,我的个人经历让我不喜欢这个goto声明,因为它不容易阅读。当我试图理解一段带有四个或五个名为some-errorfail、的标签的代码时retry,它们并不靠近有问题/异常的代码,解开它可能是一场噩梦。如果它不容易阅读,那么即使是试图回顾他或她一年前所做的事情的程序员也可能会导致错误。

作为这个答案的后记,我不认为“错误”是描述goto. 这可能来自于轰动著名的 Dijkstra 论文。“强大”可能是一个更好的词,因为它意味着它几乎可以做任何事情,但即使是最简单的错误也会使“错误”变成“可怕的错误”。我们不应该禁止工具,因为它们真的很危险;我们应该更好地教育人们了解他们的危险,让他们做出决定。

于 2011-04-20T21:58:37.050 回答
2

goto与 throw 相比可能会有一点性能提升,因为它不会创建任何异常堆栈。

于 2010-07-25T14:15:37.193 回答
2

实际上,您的两个代码段之间的区别在于您必须花费更多时间使用“goto”代码向其他程序员解释自己。

无论好坏,大多数程序员都认为你永远不应该使用 goto。也许您可以证明这是没有根据的,并且您的 goto 代码是实现该功能的最佳方式。即便如此,您仍必须与同事或与您合作的任何其他人争吵,才能让他们接受它。

如果您自己工作并且没有人会看到您的代码 - 使用您喜欢的任何内容。但如果你在一个团队中工作,有时阻力最小的路径是最谨慎的。

于 2010-07-26T13:26:21.137 回答
0

我不像很多人那样讨厌 GOTO,但我认为它的主要问题是它不限于特定的“范围”。对于所有其他控制结构,您都知道它从哪里开始和在哪里结束。另一方面,GOTO 标签可能被放置在更多不可预测的地方。您当然可以搜索标签,但它会破坏可读性。

当然,一切都取决于它的使用方式。

于 2010-07-26T17:14:43.603 回答
-3

也不要使用。在大部分代码周围使用“while(1)”,并在要跳转到末尾的地方使用“break”语句。

while(1)
{
    if (!isset($a)) { 
      $message = '$a is not set';
      break;
    }
    if (!isset($b)) {
      $message = '$b is not set';
      break;
    }

    echo '$a + $b = '.($a + $b)."<br>\n";
    break;
}

if (!empty($message)) {
  echo 'Caught exception: ', $message, "<br>\n";
}
于 2010-07-26T17:07:12.370 回答