每个人都知道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的观点,因为 他最近写道: “请不要落入。 相信我是可怕的 教条的关于[去]的教条的 声明)。我觉得很不舒服 感觉别人在做 宗教出来了,好像 编程的概念问题 只用一个小技巧就能解决,用什么 一种简单的编码纪律!”


当前回答

在我编程生涯的早期,有一次,我编写了一个由一系列链函数组成的程序,其中每个函数在给定成功条件和完成条件的情况下调用它的后继函数。

这是一个丑陋的笨拙,有许多严重的问题,最严重的是,在它下面的所有函数都终止之前,任何函数都不能终止。

但是它很快就被开发出来了,对于它所要解决的有限的问题集工作得很好,并且明确地显示了程序的逻辑和流程,当我对它进行重构和扩展以包含在另一个项目中时,它工作得很好。

我的意见是在有意义的时候使用它,并在方便的时候尽快重构它。

其他回答

原论文应该被认为是“无条件GOTO被认为是有害的”。它特别提倡一种基于条件(if)和迭代(while)结构的编程形式,而不是早期代码常见的测试-跳转。Goto在某些语言或环境中仍然有用,因为这些语言或环境不存在适当的控制结构。

有时候,在一个函数中使用GOTO作为异常处理的替代是有效的:

    if (f() == false) goto err_cleanup;
    if (g() == false) goto err_cleanup;
    if (h() == false) goto err_cleanup;
    
    return;
    
    err_cleanup:
    ...

COM代码似乎经常陷入这种模式。

虽然我认为在任何情况下都最好避免过度紧张,但也有例外。 例如,我所见过的一个地方,与其他更复杂的方法相比,goto语句是优雅的解决方案,那就是为解释器实现尾部调用消除。

Using a goto makes it far too easy to write "spaghetti code" which is not particularly maintainable. The most important rule to follow is to write readable code, but of course it depends on what the goals of the project are. As a "best practice" avoiding a goto is a good idea. It's something extreme programming types would refer to as "code smell" because it indicates that you may be doing something wrong. Using a break while looping is remarkably similar to a goto, except it isn't a goto, but again is an indication that the code may not be optimal. This is why, I believe, it is also important to not find more modern programming loopholes which are essentially a goto by a different name.

如果你用C写一个VM,使用(gcc的)计算gotos是这样的:

char run(char *pc) {
    void *opcodes[3] = {&&op_inc, &&op_lda_direct, &&op_hlt};
    #define NEXT_INSTR(stride) goto *(opcodes[*(pc += stride)])
    NEXT_INSTR(0);
    op_inc:
    ++acc;
    NEXT_INSTR(1);
    op_lda_direct:
    acc = ram[++pc];
    NEXT_INSTR(1);
    op_hlt:
    return acc;
}

工作速度比循环内的传统开关快得多。