为什么Go语言有“goto”语句?

158

我很惊讶地发现Go语言有一个'goto'语句。我一直被教导说,'goto'语句是过时的,会使程序的实际流程变得模糊,而函数或方法始终是控制流程的更好方式。

我肯定是没get到什么。为什么Google要把它包含在里面呢?


10
有时您确实需要使用goto语句。只有在滥用时,goto才是有害的。例如,如果没有使用goto语句几乎不可能编写有限状态机解析器。 - Ayush
5
这段话并非只针对Go语言,而是讨论编程语言为何保留"goto"语句以及反对使用它的理由。如果想要深入了解该话题并查看一些反对"goto"使用的论据,请参考这篇文章。该问题中还提供了一些有用的参考链接。另外,这里还有一篇相关的文章。 - Cᴏʀʏ
3
为了帮助OP避免在提供的Stack Overflow讨论中进行搜索,这里是LKML上的讨论,基本上概述了为什么在某些情况下goto很有用。请在学习Kissaki的答案后阅读。 - kostix
1
相关链接:http://programmers.stackexchange.com/q/566/33478(并查看[我的回答](http://programmers.stackexchange.com/a/133523/33478))。 - Keith Thompson
实现一个 continuation 模式非常有用,其中你保存堆栈,然后在想要恢复时返回到之前的位置。 - Justin Dennahower
3
另外需要注意的一点是,Go 没有像异常流那样的高级控制流结构,因此自然而然地需要通过使用 goto 来进行补偿。 - Ben
3个回答

106

当我们实际检查Go标准库的源代码时,可以看到goto语句实际上被很好地应用。

例如,在math/gamma.go文件中,使用了goto语句

  for x < 0 {
    if x > -1e-09 {
      goto small
    }
    z = z / x
    x = x + 1
  }
  for x < 2 {
    if x < 1e-09 {
      goto small
    }
    z = z / x
    x = x + 1
  }

  if x == 2 {
    return z
  }

  x = x - 2
  p = (((((x*_gamP[0]+_gamP[1])*x+_gamP[2])*x+_gamP[3])*x+_gamP[4])*x+_gamP[5])*x + _gamP[6]
  q = ((((((x*_gamQ[0]+_gamQ[1])*x+_gamQ[2])*x+_gamQ[3])*x+_gamQ[4])*x+_gamQ[5])*x+_gamQ[6])*x + _gamQ[7]
  return z * p / q

small:
  if x == 0 {
    return Inf(1)
  }
  return z / ((1 + Euler*x) * x)
}
在这种情况下,goto语句可以避免我们引入另一个(布尔)变量用于控制流,并在结尾处进行检查。在这种情况下goto语句使得代码实际上更易阅读和理解(与您提到的反对goto的观点相反)。
此外,请注意,goto语句具有非常特定的用例。关于goto语言规范指出,它不能跳过正在进入作用域(被声明)的变量,并且不能跳转到其他(代码)块中。

104
为什么不引入一个函数small(x,z)来调用呢?这样我们就不必考虑small:标签中哪些变量是可访问的了。我猜测原因是Go语言编译器仍缺乏某些类型的内联支持。 - Thomas Ahle
8
这是我们可以看到和掌控的范围,对吗? - Thomas Ahle
8
在引入新变量后,Go禁止goto指向标签。在执行"goto"语句时,不能使任何变量进入作用域,这些变量在goto语句的位置之前不在作用域内。参考文献:https://golang.org/ref/spec#Goto_statements - km6zla
7
抱歉,我之前表述不够清晰。我的意思是,在需要使用函数的范围内声明函数,这样它们就不会对不需要使用它们的代码可见。我没有谈论goto和作用域。 - Thomas Ahle
5
引用的代码并没有针对可读性进行优化,它优化了原始性能,使用了一个跨越单个函数内22行的goto语句。(而Thomas Ahle的提案在我看来甚至更易读。) - joel.neely
显示剩余6条评论

41

当内置的控制功能无法满足您的需求,或者您可以使用Goto表达您想要的内容时,使用Goto是一个不错的选择。(在某些语言中,如果没有Goto,您最终会滥用某些控制功能、使用布尔标志或使用比Goto更糟糕的其他解决方案,这是一件可惜的事情。)

如果其他控制功能(以合理明显的方式使用)可以实现您想要的内容,则应优先使用它,而不是使用Goto。如果没有,那就勇敢地使用Goto吧!

最后值得注意的是,Go语言的Goto有一些限制,旨在避免一些晦涩的错误。请参阅规范中的这些限制。


15
自从60年代和70年代的意大利面代码时代以来,Goto语句就受到了很多贬低。当时,几乎没有软件开发方法论。然而,Goto本质上并不邪恶,但当然可以被懒惰或技能不足的程序员滥用和误用。许多滥用Goto的问题可以通过团队代码审查等开发流程来解决。 goto是与continuebreakreturn相同技术方式的跳转语句。有人可能会认为这些语句也是邪恶的,但它们并不是。
Go团队为什么包含Goto可能是因为它是一种常见的流程控制原语。此外,他们希望得出结论:Go的范围不包括制作一个不可能被滥用的白痴安全语言。

2
continuebreakreturn在一个关键点上非常不同:它们仅指定“离开封闭范围”。它们不仅鼓励而且明确要求开发人员考虑其代码的结构并依赖于结构化编程原语(例如循环,函数和switch语句)。goto语句的唯一救赎是当编译器的优化器无法胜任时,它允许您在HLL中编写汇编代码,但这是以可读性和可维护性为代价的。 - Parthian Shot
这个在Go语言中出现的情况让人感到惊讶的原因是,在其他所有情况下,Golang开发人员在结构化编程范式和“异常流控制”/指令指针操作(如setjmplongjmpgototry/except/finally)之间有选择时,他们选择了谨慎。据我所知,goto是唯一对“结构化编程”控制流的妥协。 - Parthian Shot

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接