打个比方的话,goto就好像你开车带你朋友回家过春节,走到半道你累了,于是你goto到你朋友的位置上同时你朋友goto到你的座位上——让你朋友代替你驾车,避免你疲劳驾驶。
所以,你看,goto多好用啊。
问题是,看到goto好用之后,很多人开发了海量的另类用法——而且觉得自己很聪明。
比如,你踩了300km油门,累了。于是你拿开右脚,让你朋友把一条腿goto过来帮你踩油门。
——没错,伟大的goto也能支持这个!
再比如,开着开着你睡着了;你朋友眼疾手快,goto到你大腿上帮你稳住了方向盘——但因为地方狭窄他只能侧坐着,需要的时候就拧你一把让你的左脚goto过去踩刹车……
——没错,伟大的goto干这个不费劲!
开着开着你朋友腰酸了,而你腿也被坐麻了。怎么办呢?你goto到他上面替他抱方向盘,他goto到你下面帮你踩刹车油门……
——是的,难道这不正是goto最高明的地方吗?
goto来goto去,终于,天怒人怨:
对于一个之前接触过BASIC语言的学生,你基本上不可能教会他如何正确的编程:因为作为一个程序员苗子,他们已经脑残,无可救药。
It is practically impossible to teach good programming to students that have had a prior exposure to BASIC: as potential programmers they are mentally mutilated beyond hope of regeneration.
Dijkstra
为什么dijkstra如此痛恨basic语言呢?
他痛恨的那种basic并非我们现在所见的basic,而是早期严重依赖行号、滥用goto的basic。
https:// programmingisterrible.com /post/40132515169/dijkstra-basic
可以说,dijkstra之所以彻底否定basic、甚至极其罕见的把一门语言的所有使用者斥为垃圾,99%都是因为早期basic鼓励滥用goto。
这种语言的每行代码都有行号,你只需goto 行号就可以在代码中随意跳跃;甚至于,它的子程序也不过是行号不同的一段代码而已,你完全可以goto进某个子程序的倒数第三行、然后检测到它没有设置某个标记所以不能return于是goto回你认为的、主程序中的调用点!
次要原因是这门语言的使用者喜欢用on error resume next忽略所有错误,和其它语言使用者“把警告视为错误”的经验教训背道而驰——注意别人是“连警告都视为错误(不得忽略!)”,basic社区却习惯于忽略所有错误!
注意忽略的是错误!不是警告!
另外,因为每行都有行号且极其频繁的借助行号来回跳转,无形中驱使这门语言的使用者倾向于写超长的程序语句。因为换行越少、行号越不容易被破坏——不然哪怕你10、20的排,中间至多再插9句指令,多了就得改后面的行号;然而后面的行号可能已经在其它地方存在引用了。
总之,这门语言的设计存在严重问题,极其容易诱导它的程序员养成错误习惯;养成了错误习惯的“前辈”转回头,又把这些错误当成教条传授给后来者——反复自我强化之后,这门语言的社区甚至把各种更矫揉造作的风格当成“瑰宝”、当成“评判是否学有所成”的“金标准”!
没错,当初的basic程序普遍长这样:
10 let int i = 1 20 do sth 30 do other 40 goto 190 50 other 60 gosub 180 70 gosub 170 80 gosub 160 90 XX 100 goto 240 ... 160 let int call_flag = 1 170 print("hello world!") 180 let int call_flag2 = 2 190 what the fuck? 200 if call_flag = 1 goto 90 '不return了,将来有伏笔! 210 if call_flag2 = 2 goto 230 220 goto 40 '只有第40行会直接goto 190,直接goto回去就好了 230 goto 70 240 do other '注意这里利用了“伏笔”,所以千万别直接goto 160~180,会崩! 250 if call_flag2 = 2 return '本质上,这是把主程序中的若干行代码拿来当函数体内代码用了,因此其中引用的部分变量可能来自函数内部;return后回归本源 260 print("still alive?") 270 goto 10
疯狂不?
这可不是玩笑。
我最初接触电脑时,学校图书馆借的basic小程序方面的书,里面就充斥着这类代码!
尤其其中一个迷宫生成程序,它的代码逻辑就是这么混乱,就是这么每3、4行必有一个goto;而且的确是goto到几百里外的B地执行两三行代码,就又goto到几十里外的C地,执行两行代码后又goto到D,然后D跳E、E跳F、F突然又跳回到B点下一行,然后神奇的跳了G……最后,K处的代码利用18次跳转前一次未return的call,出乎意料的回到了C!
我们来对比一下。
if根本上就是goto,只是它只有一个出发点和两个目的地;for/while循环其实也是goto,但它也只有一个出发点和两个目的地;continue/break本质上也是goto,它为循环增加了一个出发点但目的地固定(跳循环开头或结尾)……
最变态的还是函数调用。函数调用包括现场保存和恢复两个过程,其中call隐含了现场保护而return隐含了现场恢复。因此,借助goto,你可以到处“借”代码、让主程序中的一段代码神奇的修改函数作用域的变量——然后一个return,那段代码又去修改全局变量了!那叫一个扑朔迷离!
看出来了吗?
结构化程序设计并没有、也不可能彻底禁止goto;但它尽量把goto变得可控——使用goto的目的,或者是分支选择(包括if这样的单分支选择或者switch这样的多分支选择)、或者是(跳到循环体开头)循环执行一段代码、或者是保存当前执行现场然后跳转到子程序开头(为了复用代码)……
换句话说,结构化程序设计使用goto的目的很清晰,代码组织结构也很清晰——要么分支要么循环要么复用代码(而可复用代码本身就必须事先独立出来、把入口出口参数都弄明白、同时还要起一个方便识别功能的好名字)……
就好像你可以和你朋友互换着开车、但却绝不能你对一条腿他上一条胳膊、难解难分的纠缠着开车一样!
既然必需使用goto的场景已经通过专门的、结构化的if/switch/for/while/do...while之类关键字明确支持了,不可控的、目的模糊的goto还需要吗?
经过大量辩论,业界终于确认:
1、完全杜绝使用goto是可行的。
2、但极其特殊的情况下,恰到好处的使用goto的确能把代码变得更清晰更简洁。
因此,最终,各种编程语言还是保留了goto,但并不推荐你用它。
尤其是,绝大多数语言禁止跨函数作用域goto,也就是你不能从A函数里面goto到B函数第8行。因为函数调用隐含了栈维护方面的很多动作,太容易弄出出人意料的代码。
换句话说,正确的使用goto并没有什么特别的危害;但绝大多数情况下你并不需要使用goto。
记住goto是丑陋的,仅在goto能把代码变得更清晰时使用它——请反复确认使用规范的if、for、continue不可能写的更清晰,特别要注意“区分是你自己不善于合理使用if/for等常规关键字,还是这些关键字的确解决不了你的问题”。
当你看到一个3k行的Oracle存储过程,你读了1k行,里面写了很多注释表明读过这些代码的人也搞不明白这破“子过程”在干啥所以在释放各种怨念,然后你发现1k行到3k行出现了若干goto……
然后你就停止阅读,在读到goto的地方开始释放你的情绪!!!
so...
任何一种可以被滥用的方法,最终都会被滥用。
goto可以用来替代掉if,for,while,do,函数调用,返回,等所有形式的分支与跳转。当你用这种方法组织起一个程序的时候,确实就会感觉goto很糟。
但我个人却对「goto乱象真的是由goto带来的吗」这件事表示疑问。
在我看来,真正带来乱象的问题是「大量的label」而不是goto本身。
如果一个100行的代码,仅仅在第98行有一个label。那么无论你怎么滥用goto,用十个goto,程序都不会滥倒哪儿去。
如果一个100行的代码,其中有20~50个label,那哪怕你只有3~5个goto,这代码可能已经没眼看了。
真正不该乱用的是label,而不是goto。
确实有人可以举出一些用goto但程序不糟糕的代码出来,但他们没有意识到,所有这类代码,都具有极少量的label,甚至可能只有一个label。
如果将可以放label的位置进行严格限定,那么goto也就并不是什么可怕的东西。
反过来说,当年basic那种「每一行代码都有一个label」的语言,注定会是一个噩梦。
快去玩《人力资源机器》
最后一关,区区一个排序……在若干goto嵌套的加持下,我真的要吐了
其实就是语义性。
goto本质就是流程控制,但是流程这个东西,我们已经研究的非常透彻了,基本上只有三种:
子流程、循环和条件分支。
这三者分别对应函数调用,for、while循环和if、switch。
然后我们还发明了跳过(continue)和跳出(break、return),这些流程控制符已经基本让我们可以控制所有的流程了,而且这些东西的语义比goto好得多。
所以,goto就不被推荐了,当然,本质上所有的流程控制都是用跳转实现的,所以你写一大堆goto,等于是人肉编译器,代码混淆器,底层代码生成器。
代码始终还是写给人看的。
本质就是goto的限制太弱了,限制越弱的东西,功能就越强,小范围用起来越方便,但也越容易破坏工程约束和实践。