代码中的Goto真的那么不招人喜欢么?

开发 开发工具
在程序代码中,我们为什么使用goto,或者,我们为什么不该使用goto呢?本文将讨论代码中的Goto真的那么不招人喜欢么?

几日前在Cafe午餐的时候,大家聊起一些在Windows操作系统源代码库中曾经看到过的一些趣闻逸事,比如那个著名的“becauseExchangeisamoron”(正好这天公司的Exchange服务器巨慢,所以大家更是大发一笑)的注释。这其中有人提到Windows代码中大量使用goto语句的这个事,这让我想起这样一个有趣的问题:

在程序代码中,我们为什么使用goto,或者,我们为什么不该使用goto呢?

我曾经不止一次地听某某义正言辞地向我宣传goto是邪恶的,但如果我追问这么说的理由为何时,通常的答案都是模模糊糊的人云亦云之类的回答。大部分的理由都会指出goto破坏了程序的可读性和可维护性,如果代码里到处都是goto来goto去,到***谁都很难搞清程序goto到哪一个地方了。

这看似颇有道理的说辞其实充满了迂腐的书生气。稍微有点常识的程序员,难道真会如此到处使用goto么?显然不会。如果说真的有那么一位程序员是到处在用goto把他的程序逻辑拼接起来的话,那我想他不是天才(汇编写太多了,到处都要自己跳转)就是无知(完全无法结构化自己的算法思路)。而软件开发作为一个工程行业经过这么多年的发展,现实中已经很少会真的有这种滥用goto的现象了。这当然也要感谢于那些关于goto邪恶性的大力宣传,大家上proceduralprogramming***课开始,就被反复灌输了“不要用goto,不要用goto”的观念。

那为什么Windows操作系统代码中大量使用了goto?是不是微软总部都雇佣了些烂人,大家都在混饭吃?还是说对于goto的使用是其实很有选择性的?而从当年goto的大量出现到今天这个关键词在使用C#或Java写就的程序中几乎绝迹,这一切,其实都是有其历史背景和含义的?

要回答这些问题,我们首先讨论一下goto在Windows操作系统源码中的使用。如果仔细观察一下的话,你会发现goto的使用其实都是在一种很特定的场合,那就是:系统资源的回收和释放。这里,系统资源可能是一块字符串内存,可能是某个内核对象(比如event或mutex)的句柄(handle),也可能是更复杂一些的数据结构。所以,goto出现的代码段,通常有这样的结构:

  1. voidFunc()...{  
  2. ...Magic::Initialize();  
  3. BSTRsomeString=::SysAllocString(L"Somerandomstring");  
  4. hr=CallSomeAPI();  
  5. if(FAILED(hr))  
  6. gotoEXIT;...  
  7. hr=CallSomeOtherAPI();if(FAILED(hr))  
  8. gotoEXIT;  
  9. ...  
  10. EXIT:Magic::Uninitialize();  
  11. ::SysFreeString(someString);  
  12. ...  

如此便不难理解为什么goto在这种特定情况下可以简化代码编写的结构,使之更清晰易懂了。试想如果不试用goto,我们的代码就会变成:

  1. HRESULTFunc(){  
  2. ...  
  3. Magic::Initialize();  
  4. BSTRsomeString=::SysAllocString(L"Somerandomstring");  
  5. hr=CallSomeAPI();  
  6. if(FAILED(hr))  
  7. {Magic::Uninitialize();  
  8. ::SysFreeString(someString);  
  9. returnhr;  
  10. }  
  11. ...hr=CallSomeOtherAPI();  
  12. if(FAILED(hr))  
  13. {  
  14. Magic::Uninitialize();  
  15. ::SysFreeString(someString);  
  16. returnhr;  
  17. }  
  18. ...returnS_OK;  

要做回收处理的资源越多,这样的写法就显得越冗长,因此goto在这里是很自然的一种选择。

但随着面向对象的编程模式(Object-OrientedProgrammingParadigm)逐渐地开始取代过程式编程(ProceduralProgramming),程序员开始发现有一种更好的模式(Pattern)可以用来取代goto,那就是RAII(ResourceAcquisitionIsInitialization)模式(“资源分配与初始化同步”)。RAII的主要思想在于两点:1.对象在且一定在被分配或构造(construct)的时候同时被初始化,这样就避免了资源在没有被适当初始化前就被用户调用。2.对象在被析构(destruct)的时候释放所占有的资源,这样就防止了资源泄漏。这个模式最为大家所熟知的应用可能就是C++标准库或者COM编程中随处可见的“聪明指针”(smartpointer)了。比如在上面的例子中,我们就可以定义一个MagicPtr的类,然后在类的构造函数里做Initialize,在析构函数里做Uninitialize。而对于BSTR,微软已经提供了相应的类了,那就是_bstr_t

利用goto来释放资源在proceduralprogramming的时代是一个自然的选择,所以在Windows的源代码中你会看到goto的踪影,因为Windows在OO思想大行其道之前就已经存在多年了。但随着OOP的深入人心,遵循RAII来管理资源就成为了最自然的选择。

另一个重要的原因,就是异常处理(exceptionhandling)概念的兴起。goto虽然可以很干净地解决过程式资源回收的问题,但却对异常这个东东没有很好的解决方法。比如上面的程序要是哪里抛出一个异常的话,那goto的部分就根本不会被执行了。而另一方面,RAII却能很好地解决这个问题,因为在对象离开定义域之前(不管是return了还是exceptionthrown了),析构函数都会被执行的。

其实写这篇东西的另一个目的也是想说:每一件看似简单的事情背后,如果你花一些时间去思考和研究,也许就会发现很多更深刻的意义和结果。这并不是要我们变成一个多疑的偏执狂,而是我觉得思索和提问的习惯是有益的。对于一个看似简单的道理,我们能不能提出让自己信服的佐证来,我们是否有一种直觉,告诉自己:Iamwonderingifthereismoretoit。事实上,这个世界上的偏执狂是少数,多的,是人云亦云的大众。

【前几周热点周报】

  1. 开发热点周报:ASP.NET与PHP性能大战 开源界风波再起
  2. 开发热点周报:ASP.NET MVC 2发布 STM淘汰线程锁
  3. 开发热点周报:微软示好Linux Ruby+Rails小更新
  4. 开发热点周报:甲骨文补丁日 Azure价格确定
  5. 开发热点周报:Silverlight 3发布 XHTML 2夭折
责任编辑:彭凡 来源: IT168
相关推荐

2019-12-18 15:11:42

数组集合数据

2020-03-13 14:45:14

Java枚举代码

2015-03-18 14:12:50

2016-08-16 13:30:49

LinuxCachetmpfs

2014-12-18 10:16:00

Java

2009-04-13 09:35:50

SaaS云计算缺陷

2017-09-07 16:32:05

华为

2017-03-02 13:56:45

Facebook广告归因

2019-02-13 23:03:06

IE浏览器微软

2018-09-30 09:36:58

CTO代码程序员

2019-08-27 08:24:17

简历技能工作

2021-01-11 08:03:30

阿里中台项目

2020-04-03 14:25:55

diff Meld工具

2010-02-07 13:45:12

Android操作系统

2010-01-26 09:24:53

C++语言

2012-04-16 10:15:13

JQuery插件开发

2010-03-02 10:26:32

Android系统平台

2010-01-15 16:45:35

C++语言

2020-04-24 08:15:51

代码 if else数组

2018-07-01 08:34:09

缓存数据服务
点赞
收藏

51CTO技术栈公众号