Goto真的是邪恶的吗?
很早以前的文章,翻出来让大家评价评价
有常用goto的吗
几日前在Cafe午餐的时候,大家聊起一些在Windows操作系统源代码库中曾经看到过的一些趣闻逸事,比如那个著名的“because Exchange is a moron”(正好这天公司的Exchange服务器巨慢,所以大家更是大发一笑)的注释。这其中有人提到Windows代码中大量使用goto语句的这个事,这让我想起这样一个有趣的问题:
在程序代码中,我们为什么使用goto,或者,我们为什么不该使用goto呢?
我曾经不止一次地听某某义正言辞地向我宣传goto是邪恶的,但如果我追问这么说的理由为何时,通常的答案都是模模糊糊的人云亦云之类的回答。大部分的理由都会指出goto破坏了程序的可读性和可维护性,如果代码里到处都是goto来goto去,到最后谁都很难搞清程序goto到哪一个地方了。
这看似颇有道理的说辞其实充满了迂腐的书生气。稍微有点常识的程序员,难道真会如此到处使用goto么?显然不会。如果说真的有那么一位程序员是到处在用goto把他的程序逻辑拼接起来的话,那我想他不是天才(汇编写太多了,到处都要自己跳转)就是无知(完全无法结构化自己的算法思路)。而软件开发作为一个工程行业经过这么多年的发展,现实中已经很少会真的有这种滥用goto的现象了。这当然也要感谢于那些关于goto邪恶性的大力宣传,大家上procedural programming第一课开始,就被反复灌输了“不要用goto,不要用goto”的观念。
那为什么Windows操作系统代码中大量使用了goto?是不是微软总部都雇佣了些烂人,大家都在混饭吃?还是说对于goto的使用是其实很有选择性的?而从当年goto的大量出现到今天这个关键词在使用C#或Java写就的程序中几乎绝迹,这一切,其实都是有其历史背景和含义的?
要回答这些问题,我们首先讨论一下goto在Windows操作系统源码中的使用。如果仔细观察一下的话,你会发现goto的使用其实都是在一种很特定的场合,那就是:系统资源的回收和释放。这里,系统资源可能是一块字符串内存,可能是某个内核对象(比如event或mutex)的句柄(handle),也可能是更复杂一些的数据结构。所以,goto出现的代码段,通常有这样的结构:
- C/C++ code
Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/void Func()...{... Magic::Initialize();BSTR someString = ::SysAllocString(L"Some random string");hr = CallSomeAPI();if (FAILED(hr))goto EXIT; ...hr = CallSomeOtherAPI(); if (FAILED(hr))goto EXIT;...EXIT: Magic::Uninitialize();::SysFreeString(someString);...}如此便不难理解为什么goto在这种特定情况下可以简化代码编写的结构,使之更清晰易懂了。试想如果不试用goto,我们的代码就会变成:
- C/C++ code
Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/HRESULT Func(){ ... Magic::Initialize(); BSTR someString = ::SysAllocString(L"Some random string"); hr = CallSomeAPI(); if (FAILED(hr)) { Magic::Uninitialize(); ::SysFreeString(someString); return hr; } ... hr = CallSomeOtherAPI(); if (FAILED(hr)) { Magic::Uninitialize(); ::SysFreeString(someString); return hr; } ... return S_OK;}要做回收处理的资源越多,这样的写法就显得越冗长,因此goto在这里是很自然的一种选择。
但随着面向对象的编程模式(Object-Oriented Programming Paradigm)逐渐地开始取代过程式编程(Procedural Programming),程序员开始发现有一种更好的模式(Pattern)可以用来取代goto,那就是RAII(Resource Acquisition Is Initialization)模式(“资源分配与初始化同步”)。RAII的主要思想在于两点:1. 对象在且一定在被分配或构造(construct)的时候同时被初始化,这样就避免了资源在没有被适当初始化前就被用户调用。2. 对象在被析构(destruct)的时候释放所占有的资源,这样就防止了资源泄漏。这个模式最为大家所熟知的应用可能就是C++标准库或者COM编程中随处可见的“聪明指针”(smart pointer)了。比如在上面的例子中,我们就可以定义一个MagicPtr的类,然后在类的构造函数里做Initialize,在析构函数里做Uninitialize。而对于BSTR,微软已经提供了相应的类了,那就是_bstr_t
利用goto来释放资源在procedural programming的时代是一个自然的选择,所以在Windows的源代码中你会看到goto的踪影,因为Windows在OO思想大行其道之前就已经存在多年了。但随着OOP的深入人心,遵循RAII来管理资源就成为了最自然的选择。
另一个重要的原因,就是异常处理(exception handling)概念的兴起。goto虽然可以很干净地解决过程式资源回收的问题,但却对异常这个东东没有很好的解决方法。比如上面的程序要是哪里抛出一个异常的话,那goto的部分就根本不会被执行了。而另一方面,RAII却能很好地解决这个问题,因为在对象离开定义域之前(不管是return了还是exception thrown了),析构函数都会被执行的。
其实写这篇东西的另一个目的也是想说:每一件看似简单的事情背后,如果你花一些时间去思考和研究,也许就会发现很多更深刻的意义和结果。这并不是要我们变成一个多疑的偏执狂,而是我觉得思索和提问的习惯是有益的。对于一个看似简单的道理,我们能不能提出让自己信服的佐证来,我们是否有一种直觉,告诉自己:I am wondering if there is more to it。事实上,这个世界上的偏执狂是少数,多的,是人云亦云的大众。
[解决办法]
很多经典书上都建议少量使用goto 没有说一定不要用
[解决办法]
非也
妙用
灵活
[解决办法]
存在即合理~
[解决办法]
Windows平台下感觉用__try ... __finally来实现清理资源更简单
[解决办法]
楼主举的例子可以用do...while(false)代替,当然,这里用goto是最明确的,用do...while(false)的话,有些个基础比较差的人你还得个他解释一下为什么
[解决办法]
goto至少有两种典型应用场景,这些场景下,goto是最优雅的解决方案之一
1.多层循环跳出,如果为了避免goto而罗嗦一大堆,那是神经病
2.楼主举的资源释放的例子,如果不用goto或do...while(false),反而极易引入bug,代码也混乱
这都是很基础的应用,之所以大部分应试者都不知道,只能说水平和经验还有差距,这是大学应届生和初级程序员的普遍现象
[解决办法]
高手用goto:合理
新手用goto:万恶
[解决办法]
Magic::Uninitialize();
::SysFreeString(someString);
这两个可以合并到一个函数中嘛。 这样就可以去掉goto了。
不推荐goto是怕滥用或者乱用。
[解决办法]
goto挺好,和异常处理也不冲突
[解决办法]
[解决办法]
要跳转到某个标签的时候goto是很简洁的, 不过的确有陷阱, 尤其在C++里, 使用时多检查一下.
[解决办法]
我想,经历过C和汇编的都不会对goto反感。只有那些没写过结构化程序的,整天大谈面向对象的才会说他万恶。
[解决办法]
任何东西都不邪恶,邪恶的是程序员
[解决办法]
[解决办法]
所有必须用 goto 才能做到“优雅”的地方实际上都说明了一个问题:你的代码块太庞大了,应该拆开了。
[解决办法]
不提倡滥用goto,一个版面里面跳来跳去,写的人都晕
[解决办法]
大代码块用goto是极为破坏可读性的,goto的跳转标签如果在同一屏幕看不到的话就不能用,我用goto的场景,从来都在50-100行的函数中
[解决办法]
高手用goto:合理
新手用goto:万恶
[解决办法]
实际上需要用goto的地方真的很少,不用总比滥用好,偶尔用用的确无妨,但是如果满篇goto,估计你以后自己看都费劲,所以尽量少用是没问题的
[解决办法]
这里使用goto很美
[解决办法]
知道墨菲定律么?
[解决办法]
- C/C++ code
for (int i = 1; i < 2; i++) { }
[解决办法]
用goto的人大概都很喜欢_JMP。
[解决办法]
因为宇宙里面有γ射线。
[解决办法]
万恶的不是某种模式,万恶的是不可控的状态。没有什么模式是不可控的,但也没有任何一种模式是不会陷入不可控状态的。