每个人都知道Dijkstra的《致编辑的信》:goto语句被认为是有害的(这里。html transcript和这里。pdf),从那时起,就有一种强大的推动力,尽可能避免使用goto语句。虽然可以使用goto来生成不可维护的、庞大的代码,但它仍然存在于现代编程语言中。即使Scheme中先进的连续控制结构也可以被描述为复杂的后向。

在什么情况下需要使用goto?什么时候最好避免?

作为一个后续问题:C提供了一对函数setjmp()和longjmp(),它们不仅提供了在当前堆栈帧内进行跳转的能力,还提供了在任何调用帧内进行跳转的能力。这些应该被认为和goto一样危险吗?更危险?


Dijkstra自己也对这个头衔感到后悔,因为这不是他的责任。在EWD1308的结尾(也在这里。pdf),他写道:

Finally a short story for the record. In 1968, the Communications of the ACM published a text of mine under the title "The goto statement considered harmful", which in later years would be most frequently referenced, regrettably, however, often by authors who had seen no more of it than its title, which became a cornerstone of my fame by becoming a template: we would see all sorts of articles under the title "X considered harmful" for almost any X, including one titled "Dijkstra considered harmful". But what had happened? I had submitted a paper under the title "A case against the goto statement", which, in order to speed up its publication, the editor had changed into a "letter to the Editor", and in the process he had given it a new title of his own invention! The editor was Niklaus Wirth.

Donald E. Knuth写了一篇关于这个主题的经典论文,它与Dijkstra的论文相匹配,那就是结构化编程与语句。阅读既有助于重建上下文,也有助于对主题的非教条性理解。在本文中,Dijkstra对这个案例的观点被报道,并且更加强烈:

Donald E. Knuth:我相信通过呈现这样一个 事实上,我并不是不同意 Dijkstra的观点,因为 他最近写道: “请不要落入。 相信我是可怕的 教条的关于[去]的教条的 声明)。我觉得很不舒服 感觉别人在做 宗教出来了,好像 编程的概念问题 只用一个小技巧就能解决,用什么 一种简单的编码纪律!”


当前回答

Go To可以在某些情况下为“真正的”异常处理提供一种替代品。考虑:

ptr = malloc(size);
if (!ptr) goto label_fail;
bytes_in = read(f_in,ptr,size);
if (bytes_in=<0) goto label_fail;
bytes_out = write(f_out,ptr,bytes_in);
if (bytes_out != bytes_in) goto label_fail;

显然,这段代码被简化了,以占用更少的空间,所以不要太纠结于细节。但是考虑一下我在产品代码中多次看到的另一种选择,即程序员为了避免使用goto而费尽心机:

success=false;
do {
    ptr = malloc(size);
    if (!ptr) break;
    bytes_in = read(f_in,ptr,size);
    if (count=<0) break;
    bytes_out = write(f_out,ptr,bytes_in);
    if (bytes_out != bytes_in) break;
    success = true;
} while (false);

现在这段代码在功能上做了完全相同的事情。事实上,编译器生成的代码几乎完全相同。然而,在程序员对Nogoto(可怕的学术指责之神)的热情中,这个程序员完全打破了while循环所代表的底层习惯,并对代码的可读性造成了实质性的影响。这样也好不到哪里去。

所以,这个故事的寓意是,如果你发现自己为了避免使用goto而求助于一些非常愚蠢的事情,那么就不要这样做。

其他回答

看这个,这是一个很好的使用GoTo,但在一个有垃圾收集器的语言中,我认为使用GoTo的唯一原因是混淆你的代码(混淆工具使用GoTo来隐藏他们的代码)。

我们已经讨论过这个问题了,我坚持我的观点。

此外,我受够了人们将高级语言结构描述为“伪装的goto”,因为他们显然根本没有抓住重点。例如:

即使Scheme中先进的连续控制结构也可以被描述为复杂的后向。

那完全是胡说八道。每个控制结构都可以在goto方面实现,但这个观察完全是微不足道和无用的。Goto被认为是有害的,因为它的积极影响,而是因为它的消极后果,这些已经被结构化编程消除了。

Similarly, saying “GOTO is a tool, and as all tools, it can be used and abused” is completely off the mark. No modern construction worker would use a rock and claim it “is a tool.” Rocks have been replaced by hammers. goto has been replaced by control structures. If the construction worker were stranded in the wild without a hammer, of course he would use a rock instead. If a programmer has to use an inferior programming language that doesn't have feature X, well, of course she may have to use goto instead. But if she uses it anywhere else instead of the appropriate language feature she clearly hasn't understood the language properly and uses it wrongly. It's really as simple as that.

在我看来,“goto有害”更多的是关于状态的封装和一致性。

许多代码,甚至是'oo'代码,都有像意大利面条代码一样糟糕的混乱状态封装。

“goto有害”的问题是,它让程序员只看机制规则而不理解这样的印象,即唯一可用的流控制应该是返回方法,这很容易导致通过引用传递许多状态——这又导致缺乏状态封装,而这正是“goto有害”试图摆脱的东西。

遵循典型的“OO”代码库中的控制流,并告诉我我们仍然没有意大利面条代码....(顺便说一下,我并不是指那些经常让人讨厌的“馄饨”代码——馄饨代码的执行路径通常是非常简单的,即使对象关系不是立即明显的)。

或者,换一种说法,避免gotos而将所有东西都作为子例程,只有在每个子例程只修改局部状态时才有用,只有通过该子例程(或至少该对象)才能修改局部状态。

跳跃的例子在Java字符串类源代码:

int firstUpper;

/* Now check if there are any characters that need to be changed. */
scan: {
    for (firstUpper = 0 ; firstUpper < count; ) {
         char c = value[offset+firstUpper];
         if ((c >= Character.MIN_HIGH_SURROGATE) &&
                 (c <= Character.MAX_HIGH_SURROGATE)) {
             int supplChar = codePointAt(firstUpper);
             if (supplChar != Character.toLowerCase(supplChar)) {
                  break scan;
             }
             firstUpper += Character.charCount(supplChar);
         } else {
             if (c != Character.toLowerCase(c)) {
                  break scan;
             }
             firstUpper++;
         }
     }
     return this;
}
[... subsequent use of firstUpper ...]

这可以用很少的开销重写,例如:

 int firstUpper = indexOfFirstUpper();
 if (firstUpper < 0) return this; 

即使在现代语言中,即使我实际上不喜欢使用gotos,但我认为它们在许多情况下是可以接受的,在像这样的低级情况下,我看起来更好(它不仅仅是退出循环)。

没有激起宗教战争的意图。

您可以使用它来打破深度嵌套循环,但大多数情况下,无需深度嵌套循环,代码可以重构得更干净。