关于go:惊了goto-语句让-Go-代码变成意大利面条吗

27次阅读

共计 2430 个字符,预计需要花费 7 分钟才能阅读完成。

大家好,我是煎鱼。

Goto 语句在社区的探讨中常常被人诟病,认为其毁坏了结构化编程和程序的形象,是无害的,可怕的。

最早的观点来源于 1968 年,Edsger Dijkstra 写了一封信《Go To Statement Considered Harmful》,来表白其是无害的观点。

如下图:

不过,然而,其实 …

Go 反对了 goto 语句,很多人不了解,大喊 less is more 的 Go Team 竟然加了 …

明天就由煎鱼带大家看看。

Goto 语法

Goto 的语法格局,如下:

goto label
...
...
label: statement

代码案例,如下:

package main

import "fmt"

func main() {learnGoTo()
}

func learnGoTo() {fmt.Println("a")
    goto FINISH
    fmt.Println("b")
FINISH:
    fmt.Println("c")
}

上述代码在函数 learnGoTo 中先输入了 a,而后到了 goto FINISH 代码段,因而间接跳到了 c 的输入,所以 b 的输入代码被间接跳过。

输入后果:

a
c

Goto 的危害

Goto 的危害所带来的一个经典名称是:Spaghetti code(意大利面条代码),指的是对非结构化和难以保护的源代码的贬义词。

这样的代码具备简单而纠结的控制结构,导致程序流程在概念上就像一碗意大利面,扭曲和纠结。

参考代码如下:

  INPUT "How many numbers should be sorted?"; T
  DIM n(T)
  FOR i = 1 TO T
    PRINT "NUMBER:"; i
    INPUT n(i)
  NEXT i
  'Calculations:
  C = T
 E180:
  C = INT(C / 2)
  IF C = 0 THEN GOTO C330
  D = T - C
  E = 1
 I220:
  f = E
 F230:
  g = f + C
  IF n(f) > n(g) THEN SWAP n(f), n(g)
  f = f - C
  IF f > 0 THEN GOTO F230
  E = E + 1
  IF E > D THEN GOTO E180
 GOTO I220
 C330:
  PRINT "The sorted list is"
  FOR i = 1 TO T
    PRINT n(i)
  NEXT i

下面这个例子,你能看到 goto 语句可能在 任意控制流 中到处流转,你可能还得记住它的标签是什么,跳到哪里。

程序员还要起出各种名字,例如:煎鱼哥哥、煎鱼弟弟、煎鱼敌人。起名的灵感是贫乏的,很容易凌乱。

真实世界中长期倒退的业务代码,滥用 goto 语句可能会更重大。

Goto 存在的意义

Go Spec

实际上在 Go 中,Goto 语句与其余语言相比有着更加严格的限度,在 Go Spec《Goto statements》中进行了用法的阐明。

标准要求在 goto 语句的作用域范畴内不能有任何变量申明等动作,是坏滋味。

如下代码:

    goto L  // BAD
    v := 3
L:

因为这会导致变量 v 的申明被跳过。

同时要求代码块外的 goto 语句不能跳转到另外一块代码块内的标签。

如下代码:

if n%2 == 1 {goto L1}
for n > 0 {f()
    n--
L1:
    f()
    n--
}

不能从 if 代码块横跨作用域到 for 代码块。

Go 规范库源码例子

能够看看 Go 规范库中的 math/gamma.go 源代码,是一个很不错的案例。

如下代码:

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 到处乱飞,当然是不合理的。

但这其实又两派观点,就如咱们之前文章的读者所提到:

能够怪程序员写出意大利面条,也能够寄望语言层面躲避,这样能够做的更好,不须要每一个新来的程序员都要从新造就意识。

Go 也会在 break 中反对标签跳转,与 goto 的用法是类似的:

Loop:
    for {
        select {
            ...
            break Loop
        }
    }

Go Team 显然抉择了语言层面去躲避 goto 的局部简单场景,束缚了只能在一个代码块进行 goto 跳转,这样可能领有更好的可读性,也能失去相应的价值。

总结

一个新的关键字的产生,必然蕴含其背景的起因和行为。如果只是一味地一刀切,最初必定会解决了个寂寞。

通过这近 60 年的计算机行业的 goto 常识陶冶和思考,大家曾经意识到 goto 在任意控制流中乱跳是十分恶心的。包含世界上最好的语言 PHP,其实在 5.3.0 起,也谨慎的退出了 goto,也是带限度的,范畴是同一个文件和作用域。

新的 goto 状态,是这种带限度的 goto 模式的摸索。你感觉怎么样?

If you need to go to somewhere, goto is the way to go. —— Ken Thompson

文章继续更新,能够微信搜【脑子进煎鱼了】浏览,本文 GitHub github.com/eddycjy/blog 已收录,学习 Go 语言能够看 Go 学习地图和路线,欢送 Star 催更。

Go 图书系列

  • Go 语言入门系列:初探 Go 我的项目实战
  • Go 语言编程之旅:深刻用 Go 做我的项目
  • Go 语言设计哲学:理解 Go 的为什么和设计思考
  • Go 语言进阶之旅:进一步深刻 Go 源码

举荐浏览

  • Go 设计哲学:少即是多,哪里来的?
  • 为什么 Go 有两种申明变量的形式,有什么区别,哪种好?

正文完
 0