代碼中的Goto真的那么不招人喜歡么?
幾日前在Cafe午餐的時(shí)候,大家聊起一些在Windows操作系統(tǒng)源代碼庫中曾經(jīng)看到過的一些趣聞逸事,比如那個(gè)著名的“becauseExchangeisamoron”(正好這天公司的Exchange服務(wù)器巨慢,所以大家更是大發(fā)一笑)的注釋。這其中有人提到Windows代碼中大量使用goto語句的這個(gè)事,這讓我想起這樣一個(gè)有趣的問題:
在程序代碼中,我們?yōu)槭裁词褂胓oto,或者,我們?yōu)槭裁床辉撌褂胓oto呢?
我曾經(jīng)不止一次地聽某某義正言辭地向我宣傳goto是邪惡的,但如果我追問這么說的理由為何時(shí),通常的答案都是模模糊糊的人云亦云之類的回答。大部分的理由都會(huì)指出goto破壞了程序的可讀性和可維護(hù)性,如果代碼里到處都是goto來goto去,到***誰都很難搞清程序goto到哪一個(gè)地方了。
這看似頗有道理的說辭其實(shí)充滿了迂腐的書生氣。稍微有點(diǎn)常識(shí)的程序員,難道真會(huì)如此到處使用goto么?顯然不會(huì)。如果說真的有那么一位程序員是到處在用goto把他的程序邏輯拼接起來的話,那我想他不是天才(匯編寫太多了,到處都要自己跳轉(zhuǎn))就是無知(完全無法結(jié)構(gòu)化自己的算法思路)。而軟件開發(fā)作為一個(gè)工程行業(yè)經(jīng)過這么多年的發(fā)展,現(xiàn)實(shí)中已經(jīng)很少會(huì)真的有這種濫用goto的現(xiàn)象了。這當(dāng)然也要感謝于那些關(guān)于goto邪惡性的大力宣傳,大家上proceduralprogramming***課開始,就被反復(fù)灌輸了“不要用goto,不要用goto”的觀念。
那為什么Windows操作系統(tǒng)代碼中大量使用了goto?是不是微軟總部都雇傭了些爛人,大家都在混飯吃?還是說對(duì)于goto的使用是其實(shí)很有選擇性的?而從當(dāng)年goto的大量出現(xiàn)到今天這個(gè)關(guān)鍵詞在使用C#或Java寫就的程序中幾乎絕跡,這一切,其實(shí)都是有其歷史背景和含義的?
要回答這些問題,我們首先討論一下goto在Windows操作系統(tǒng)源碼中的使用。如果仔細(xì)觀察一下的話,你會(huì)發(fā)現(xiàn)goto的使用其實(shí)都是在一種很特定的場合,那就是:系統(tǒng)資源的回收和釋放。這里,系統(tǒng)資源可能是一塊字符串內(nèi)存,可能是某個(gè)內(nèi)核對(duì)象(比如event或mutex)的句柄(handle),也可能是更復(fù)雜一些的數(shù)據(jù)結(jié)構(gòu)。所以,goto出現(xiàn)的代碼段,通常有這樣的結(jié)構(gòu):
- voidFunc()...{
- ...Magic::Initialize();
- BSTRsomeString=::SysAllocString(L"Somerandomstring");
- hr=CallSomeAPI();
- if(FAILED(hr))
- gotoEXIT;...
- hr=CallSomeOtherAPI();if(FAILED(hr))
- gotoEXIT;
- ...
- EXIT:Magic::Uninitialize();
- ::SysFreeString(someString);
- ...
- }
如此便不難理解為什么goto在這種特定情況下可以簡化代碼編寫的結(jié)構(gòu),使之更清晰易懂了。試想如果不試用goto,我們的代碼就會(huì)變成:
- HRESULTFunc(){
- ...
- Magic::Initialize();
- BSTRsomeString=::SysAllocString(L"Somerandomstring");
- hr=CallSomeAPI();
- if(FAILED(hr))
- {Magic::Uninitialize();
- ::SysFreeString(someString);
- returnhr;
- }
- ...hr=CallSomeOtherAPI();
- if(FAILED(hr))
- {
- Magic::Uninitialize();
- ::SysFreeString(someString);
- returnhr;
- }
- ...returnS_OK;
- }
要做回收處理的資源越多,這樣的寫法就顯得越冗長,因此goto在這里是很自然的一種選擇。
但隨著面向?qū)ο蟮木幊棠J剑∣bject-OrientedProgrammingParadigm)逐漸地開始取代過程式編程(ProceduralProgramming),程序員開始發(fā)現(xiàn)有一種更好的模式(Pattern)可以用來取代goto,那就是RAII(ResourceAcquisitionIsInitialization)模式(“資源分配與初始化同步”)。RAII的主要思想在于兩點(diǎn):1.對(duì)象在且一定在被分配或構(gòu)造(construct)的時(shí)候同時(shí)被初始化,這樣就避免了資源在沒有被適當(dāng)初始化前就被用戶調(diào)用。2.對(duì)象在被析構(gòu)(destruct)的時(shí)候釋放所占有的資源,這樣就防止了資源泄漏。這個(gè)模式最為大家所熟知的應(yīng)用可能就是C++標(biāo)準(zhǔn)庫或者COM編程中隨處可見的“聰明指針”(smartpointer)了。比如在上面的例子中,我們就可以定義一個(gè)MagicPtr的類,然后在類的構(gòu)造函數(shù)里做Initialize,在析構(gòu)函數(shù)里做Uninitialize。而對(duì)于BSTR,微軟已經(jīng)提供了相應(yīng)的類了,那就是_bstr_t
利用goto來釋放資源在proceduralprogramming的時(shí)代是一個(gè)自然的選擇,所以在Windows的源代碼中你會(huì)看到goto的蹤影,因?yàn)閃indows在OO思想大行其道之前就已經(jīng)存在多年了。但隨著OOP的深入人心,遵循RAII來管理資源就成為了最自然的選擇。
另一個(gè)重要的原因,就是異常處理(exceptionhandling)概念的興起。goto雖然可以很干凈地解決過程式資源回收的問題,但卻對(duì)異常這個(gè)東東沒有很好的解決方法。比如上面的程序要是哪里拋出一個(gè)異常的話,那goto的部分就根本不會(huì)被執(zhí)行了。而另一方面,RAII卻能很好地解決這個(gè)問題,因?yàn)樵趯?duì)象離開定義域之前(不管是return了還是exceptionthrown了),析構(gòu)函數(shù)都會(huì)被執(zhí)行的。
其實(shí)寫這篇東西的另一個(gè)目的也是想說:每一件看似簡單的事情背后,如果你花一些時(shí)間去思考和研究,也許就會(huì)發(fā)現(xiàn)很多更深刻的意義和結(jié)果。這并不是要我們變成一個(gè)多疑的偏執(zhí)狂,而是我覺得思索和提問的習(xí)慣是有益的。對(duì)于一個(gè)看似簡單的道理,我們能不能提出讓自己信服的佐證來,我們是否有一種直覺,告訴自己:Iamwonderingifthereismoretoit。事實(shí)上,這個(gè)世界上的偏執(zhí)狂是少數(shù),多的,是人云亦云的大眾。
【前幾周熱點(diǎn)周報(bào)】