问题

为什么说 goto 是一种不好的用法?

回答
谈到 `goto` 语句,在许多编程指南和经验丰富的开发者那里,它往往会招来一片“声讨”之声。这并非空穴来风,而是基于它在软件工程实践中长期暴露出的弊端所形成的共识。如果说编写代码是一种建造一座稳定、易于维护的数字房屋,那么 `goto` 就像是在这栋房子里乱挖地道,虽然能让你瞬间抵达某个房间,但却极大地破坏了整体的结构,让日后的修缮和扩建变得异常困难,甚至可能导致整个房子坍塌。

究竟是什么让 `goto` 如此“不受待见”?我们可以从几个核心层面来剖析:

1. 破坏了程序的结构和可读性:让代码变成“意大利面条”

想象一下,你正在阅读一份指示,告诉你先做第一步,然后跳到第五步,接着再跳回第二步,最后又去执行第十步。这样的指示是不是让人晕头转向,难以理解流程? `goto` 语句正是将这种混乱的逻辑直接注入到了代码中。

非线性控制流: 程序的正常执行顺序是线性的,一条指令接着一条指令。而 `goto` 允许你随意地跳到程序的任何一个标记(label)处执行。这种非线性的跳转打破了代码的自然流程,使得理解代码的执行路径变得异常困难。开发者需要花费大量的时间和精力去跟踪那些“跳跃”的路径,就像在意大利面条堆里找出某根面条的起点和终点一样,非常耗时且容易出错。
难以追踪的逻辑: 当一个程序充斥着 `goto` 跳转时,其控制流图(Control Flow Graph, CFG)会变得非常复杂,充满了交叉和回溯。这使得调试和理解代码的意图成为一项艰巨的任务。你可能需要一次又一次地从头开始阅读代码,才能勉强跟上那些被 `goto` 打断的逻辑。
缺乏结构化编程的优势: 结构化编程(Structured Programming)的核心思想是通过顺序、选择(if/else, switch)和循环(for, while)等结构来组织代码,使得程序的流程清晰、易于理解和管理。`goto` 直接绕过了这些结构,使得代码失去了结构化的优点,也违背了结构化编程的初衷。

2. 增加了维护的难度和出错的可能性

代码的生命周期远不止编写那么简单,绝大部分时间花在了维护上。`goto` 的存在,让后续的维护工作雪上加霜。

修改的风险: 当你需要修改一段使用 `goto` 的代码时,你必须非常小心。改变一个跳转目标,可能会牵一发而动全身,影响到其他原本正常的逻辑。你必须仔细分析所有可能受影响的 `goto` 语句,并逐一验证。这就像在你已经搭好的高楼里拆掉一根承重梁,你需要确保其他梁柱还能支撑住。
引入新的 Bug: 正是因为 `goto` 导致的逻辑复杂性,修改时极易引入新的 bug。你可能以为只是一个小小的改动,但由于 `goto` 隐藏的复杂依赖关系,一个小改动可能就会导致意想不到的连锁反应。
重构的噩梦: 如果你想对一段充斥着 `goto` 的代码进行重构,使其更易读、更模块化,那将是一项几乎不可能完成的任务。你需要先将所有 `goto` 跳转整理成结构化的逻辑,这本身就需要花费大量精力,甚至不如直接重写。

3. 隐藏了函数的边界和作用域:模糊了代码的“边界感”

函数(或方法)是我们组织代码的基本单元,它们有清晰的输入、输出和作用域。`goto` 能够将程序的控制流直接跳出函数,或者在函数内部随意跳转,这会模糊函数的边界和作用域。

跳出函数的危险: 有些编程语言允许 `goto` 跳转到函数体之外,或者在函数调用之间进行跳转。这会使得函数的封装性被破坏,导致变量状态的混乱,难以预测函数的行为。
局部变量的生命周期问题: 如果 `goto` 跳转到了某个局部变量定义之后,而你却期望在跳转的那个点使用这个变量,那么这个变量可能还没有被初始化,或者它的生命周期已经结束了。这会引发不可预测的行为和错误。

4. 在现代编程语言中,几乎总有更好的替代方案

随着编程语言的发展,许多结构化的编程 constructs 已经变得非常成熟和强大,它们能够完美地替代 `goto` 的功能,同时带来更好的可读性和可维护性。

循环语句(`for`, `while`, `dowhile`): 用于重复执行一段代码,可以很好地处理需要反复执行的逻辑,而不需要 `goto` 来跳转到循环的开始或结束。
条件语句(`if`, `else if`, `else`, `switch`): 用于根据条件执行不同的代码块,可以清晰地表达分支逻辑,避免了使用 `goto` 进行的混乱跳转。
函数/方法调用: 将重复或复杂的逻辑封装成函数,然后通过调用来执行,这样可以提高代码的模块化和可重用性。
异常处理(`trycatchfinally`): 对于错误处理和异常路径,结构化的异常处理机制比 `goto` 跳到一个特定的错误处理标签更加清晰和安全。
`break` 和 `continue`: 在循环中,`break` 可以提前退出循环,`continue` 可以跳过当前循环的剩余部分,进入下一次循环。它们提供了结构化的退出和跳过循环的方式,避免了使用 `goto` 来实现类似的逻辑。

历史遗留与特定场景的辩护(但依然需要谨慎)

不可否认,在早期的一些编程语言(如早期的 C、BASIC)中,`goto` 曾被广泛使用,尤其是在没有高级控制结构的情况下,它被用来模拟循环和条件跳转。

在极少数非常特殊的情况下,一些开发者可能会为 `goto` 辩护,例如在处理非常底层或性能极其敏感的代码中,某个特定的 `goto` 跳转能够显著简化逻辑或提高效率,并且这种简化是可以被充分理解和文档化的。例如,在一个非常长的函数中,如果你需要在多个地方跳到同一个清理代码块(比如释放资源),使用一个 `goto` 跳转到清理代码可能会比重复编写清理代码要“简洁”一些。

然而,即使在这些罕见的“合理”场景下,也需要极其谨慎。 你需要权衡这种“简洁”带来的可读性和维护性损失。更重要的是,现代的语言特性和设计模式通常能够提供更优雅的解决方案。例如,对于资源清理,RAII(Resource Acquisition Is Initialization)或者 `finally` 块通常是更好的选择。

总结

总而言之,`goto` 之所以被普遍认为是“不好”的用法,是因为它:

制造了难以理解的“意大利面条式”代码,严重破坏了程序的结构和可读性。
极大地增加了代码的维护难度和出错的可能性。
模糊了函数边界和作用域,使得程序行为难以预测。
在现代编程中,几乎总有更清晰、更结构化、更易于维护的替代方案。

除非你确信在某个极端且经过深思熟虑的情况下,`goto` 能够带来压倒性的优势,并且你能够完美地控制其带来的副作用,否则,还是请你将其束之高阁,拥抱那些让你的代码更健康、更长寿的结构化编程方式吧。记住,代码的可读性和可维护性,往往比一时的“简洁”或“效率”更为重要。

网友意见

user avatar

打个比方的话,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。

programmingisterrible.com


可以说,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等常规关键字,还是这些关键字的确解决不了你的问题”。

user avatar

当你看到一个3k行的Oracle存储过程,你读了1k行,里面写了很多注释表明读过这些代码的人也搞不明白这破“子过程”在干啥所以在释放各种怨念,然后你发现1k行到3k行出现了若干goto……

然后你就停止阅读,在读到goto的地方开始释放你的情绪!!!

so...

user avatar

任何一种可以被滥用的方法,最终都会被滥用。

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」的语言,注定会是一个噩梦。

user avatar

快去玩《人力资源机器》

最后一关,区区一个排序……在若干goto嵌套的加持下,我真的要吐了

user avatar

其实就是语义性。


goto本质就是流程控制,但是流程这个东西,我们已经研究的非常透彻了,基本上只有三种:

子流程、循环和条件分支。


这三者分别对应函数调用,for、while循环和if、switch。

然后我们还发明了跳过(continue)和跳出(break、return),这些流程控制符已经基本让我们可以控制所有的流程了,而且这些东西的语义比goto好得多。


所以,goto就不被推荐了,当然,本质上所有的流程控制都是用跳转实现的,所以你写一大堆goto,等于是人肉编译器,代码混淆器,底层代码生成器。

代码始终还是写给人看的。

user avatar

本质就是goto的限制太弱了,限制越弱的东西,功能就越强,小范围用起来越方便,但也越容易破坏工程约束和实践。

类似的话题

  • 回答
    谈到 `goto` 语句,在许多编程指南和经验丰富的开发者那里,它往往会招来一片“声讨”之声。这并非空穴来风,而是基于它在软件工程实践中长期暴露出的弊端所形成的共识。如果说编写代码是一种建造一座稳定、易于维护的数字房屋,那么 `goto` 就像是在这栋房子里乱挖地道,虽然能让你瞬间抵达某个房间,但却.............
  • 回答
    这个问题触及了两种编程范式和不同抽象层级的核心差异,也是理解底层计算机运作原理与高级语言设计哲学的一把钥匙。汇编语言:直接控制,微观的精妙在汇编语言层面,你直接与计算机的CPU打交道。CPU执行指令时,有一个叫做“程序计数器”(Program Counter,PC)的寄存器,它存放着下一条要执行的指.............
  • 回答
    关于近代历史人物是否能够“翻案”的问题,需要结合历史背景、人物行为对国家和民族的影响,以及历史评价的客观性进行分析。袁世凯和汪精卫作为中国近代史上的重要人物,其历史评价确实存在复杂性和争议性,但“不能翻案”的结论并非基于单一因素,而是综合历史、政治、道德等多方面考量的结果。以下从历史背景、人物行为、.............
  • 回答
    “明实亡于万历”这一说法是明史研究中的重要观点,主要指明朝在万历皇帝(15721620年在位)统治期间,其政治、经济、军事和社会结构逐渐崩溃,为明朝的灭亡埋下了伏笔。以下从多个角度详细分析这一观点的依据: 一、政治腐败与君主怠政:朝政瘫痪1. 万历皇帝的怠政 万历皇帝自1582年起,长期不上.............
  • 回答
    唐朝(618年-907年)的骑兵力量在历史上确实堪称“恐怖”,其强大的骑兵体系不仅在唐朝时期维持了帝国的强盛,也对周边民族和政权构成了巨大威胁。以下从多个维度详细分析唐朝骑兵为何如此强大: 一、制度保障:府兵制与募兵制的结合1. 府兵制(618年-742年) 特点:士兵平时务农,战时出征,.............
  • 回答
    在中国社会中,“无神论者”这一概念的形成与历史、文化、哲学、社会结构等多重因素密切相关。以下从多个角度详细分析中国人为何常被归类为无神论者: 一、历史与哲学传统:无神论的根源1. 儒家思想的世俗化 儒家是中国传统文化的核心,其核心理念如“仁”“礼”“义”等,强调人与人之间的伦理关系,而非对神.............
  • 回答
    中国被称为“基建狂魔”,主要源于其在基础设施领域的巨大投入、快速扩张和全球领先的成就。这一称号不仅反映了中国在经济发展中的核心驱动力,也体现了其在全球化进程中对国际社会的深远影响。以下从多个维度详细解析这一现象: 一、交通基础设施:全球最大的基建网络1. 高速铁路系统 规模与速度:中国高铁.............
  • 回答
    工人阶级被马克思主义理论视为“最革命的阶级”,这一论断源于其在资本主义社会中的特殊地位、阶级矛盾的尖锐性以及历史发展的必然性。以下从多个维度详细阐释这一观点: 一、阶级矛盾的尖锐性:经济基础与生产关系的对立1. 生产资料的占有关系 在资本主义社会中,生产资料(如工厂、机器、土地等)由资本家私.............
  • 回答
    PlayStation 5(简称PS5)被称为“土豪的玩具”这一说法主要源于其高昂的价格、性能配置与用户需求之间的差距、独占内容的高门槛,以及社会文化对消费符号的认知。以下是具体原因的深入分析: 1. 高昂的硬件成本 (1)主机本身价格昂贵 基础版售价:PS5的标准版在多数地区定价为499美元(约3.............
  • 回答
    “南美是美国的后花园”这一说法源于历史上美国对拉丁美洲国家在政治、经济、军事等多方面的深刻影响和长期主导地位。这种比喻形象地反映了美国在该地区的特权性存在与利益纠葛,其背后涉及复杂的历史背景、地缘战略以及制度性权力关系。以下从多个维度详细分析这一现象的成因: 一、历史渊源:门罗主义与“后院”概念的起.............
  • 回答
    关于“汪曾祺是中国最后一个士大夫”的说法,这一评价并非出自官方或学术界的普遍共识,而是源于部分评论家和文学研究者对其作品、人生观及文化精神的解读。这一称谓背后,蕴含着对传统文人精神在现代中国语境中逐渐消逝的感慨,也体现了汪曾祺个人独特的精神气质与艺术追求。以下从多个维度深入分析这一说法的由来及其内涵.............
  • 回答
    《老友记》(Friends)之所以被誉为经典,绝非偶然。它在播出二十多年后,依然能够吸引新一代的观众,并在流行文化中占据重要地位,这背后有着多方面的原因。我们可以从以下几个维度来详细解读:1. 对准了“青年迷茫与友情共生”的时代痛点,引发广泛共鸣: 定位的精准性: 《老友记》的故事背景设定在90年代.............
  • 回答
    资本主义的民主、自由、平等思想在实践中常常被批评为具有欺骗性,这并不是说这些理念本身毫无价值,而是指在资本主义的运行机制下,这些理念的实现往往受到限制,并且可能被用来掩盖或合理化社会不平等。以下是详细的分析:一、 民主的欺骗性:形式民主与实质民主的鸿沟资本主义框架下的民主,通常强调“形式民主”,即公.............
  • 回答
    “中国是世界上唯一一个文明没有中断的国家”是一个广为流传的说法,但它需要更细致的理解和辩证的看待。这个说法的主要依据是中国文化和政治连续性强,主体文明从未被外来文明彻底取代,并且其历史记录能够追溯到非常古老的时期。然而,其他文明古国也经历过辉煌的时期,并且它们的影响至今仍在,只是在某些方面可能经历了.............
  • 回答
    “资产阶级思想必然溶化在每一个知识分子的血液里”这种说法,在马克思主义的语境下,是一种对社会结构和意识形态相互作用的深刻洞察。它并非简单地指知识分子个人品德或忠诚度的问题,而是指向了在资本主义社会结构下,知识分子所处的环境、接受的教育、以及其赖以生存和发展的物质基础,如何不可避免地受到资产阶级思想的.............
  • 回答
    《流浪地球》之所以被许多人认为是一部“浪漫主义”作品,主要体现在以下几个方面,并且这些方面相互关联,共同构建了影片独特的情感基调和精神内核:1. 牺牲与奉献的宏大叙事: 对全人类的爱与责任感: 这是《流浪地球》最核心的浪漫主义体现。面对太阳即将毁灭的绝境,人类并没有选择自生自灭,而是选择了“带着地球.............
  • 回答
    关于美国死刑成本比终身监禁更高,以及终身监禁成本更低的说法,这背后涉及到一系列复杂的计算和司法程序。以下将详细阐述其原因和计算方式:为什么说美国死刑成本更高?美国死刑的成本之所以普遍高于终身监禁,主要是因为死刑案件在整个司法程序中需要经历更漫长、更复杂、更耗时、更昂贵的审查和上诉过程。这些额外的成本.............
  • 回答
    “永远不要考验人性”这句话之所以流传广泛且深入人心,是因为它蕴含着对人性复杂性、脆弱性以及潜在负面影响的深刻洞察。从多个角度来理解,我们可以更详细地阐述其含义:一、人性的复杂性与多面性: 善恶并存: 人性并非非黑即白。每个人内心都可能同时存在善良、同情、慷慨等积极品质,也存在自私、贪婪、嫉妒、冷.............
  • 回答
    “中国人缺少创造力”这一说法,在不同的历史时期和不同的语境下,曾被广泛讨论和提出,但它本身是一个非常复杂且带有一定主观性的论断,需要进行更细致的分析。为什么会有“中国人缺少创造力”的说法?这种说法通常源于以下几个方面的原因:1. 历史上的“中学为体,西学为用”的思维模式: 在近代中国,面对西方工业.............
  • 回答
    “这个时代,寒门再难出贵子”这句说法,并非绝对的真理,但它深刻地反映了当前社会结构性问题对个体发展机会的不平等影响。这句话的流行,源于对过去几十年中国社会变迁的观察,以及对当下教育、经济和社会资源分配公平性的担忧。下面我将从几个主要方面来详细阐述这个说法的成因和内涵:一、 教育资源分配的不均是核心原.............

本站所有内容均为互联网搜索引擎提供的公开搜索信息,本站不存储任何数据与内容,任何内容与数据均与本站无关,如有需要请联系相关搜索引擎包括但不限于百度google,bing,sogou

© 2025 tinynews.org All Rights Reserved. 百科问答小站 版权所有