標籤:

為什麼說goto是一種不好的用法?

記得編程書上都不推薦用goto語句的,但是看很多源代碼,goto也用的很多。有時候自己寫一個模塊,也覺的此時用goto是最方便的一種方式。那麼,又是從什麼方面,使得goto語句不被鼓勵使用呢?是維護性,或編譯器的角度等等?


當你看到一個3k行的Oracle存儲過程,你讀了1k行,裡面寫了很多注釋表明讀過這些代碼的人也搞不明白這破「子過程」在幹啥所以在釋放各種怨念,然後你發現1k行到3k行出現了若干goto……

然後你就停止閱讀,在讀到goto的地方開始釋放你的情緒!!!

so...


這個說法最早是由荷蘭著名計算機科學家E.W.Dijkstra於1968年提出的,在一封給ACM編輯的信《go to statement considered harmful》中指出。

當時已經證明,任何程序可以通過順序、選擇分支和循環三種方式組成,也就是,只需要if...else和while就是足夠的。提倡使用這三種方式進行結構化編程,是提高程序質量的一種方法。Dijkstra的詳細理由,大意是提高程序質量、明晰程序流程,可以參見原文,http://ce.sharif.edu/courses/90-91/1/ce364-1/resources/root/GoTo/Dijkstra.pdf


先貼一張圖。

首先很多人已經認為goto是有害的了,最出名的就是Dijkstra的那篇《go to statement considered harmful》:http://ce.sharif.edu/courses/90-91/1/ce364-1/resources/root/GoTo/Dijkstra.pdf,另外很多語言對於goto的用法都是消極態度。例如在ruby裡面吧goto放到了Library/Evil裡面,見:http://raa.ruby-lang.org/project/ruby-goto/

主要批評goto的有2個原因:

  1. 降低代碼的可讀性。很多人管這類代碼叫做Spaghetti code,說的就是代碼寫出來像是這麼的一坨:

    另外早期像Basic這樣頻繁使用goto的語言都是有行號的,如果大量使用goto的話可能會造成代碼的行號異常複雜,更加難讀。但對於這個觀點,我有個小吐槽,代碼的易讀性還是建立在寫程序人的習慣上,像我看很多人的代碼,就跟看ioccc的代碼沒什麼區別: http://www0.us.ioccc.org/years.html
  2. 現在程序語言普遍的做法是用if-then-else這種結構來代替goto。因為認為大部分程序都可以通過避免使用goto來實現,以至於現在潛移默化的似的計算機語言往結構更加清晰的方向去發展,人們管這種編程思想叫做Structured programming。

但也不是絕對的,例如高德納就在《Structured Programming with go to Statements》(http://pplab.snu.ac.kr/courses/adv_pl05/papers/p261-knuth.pdf)寫到過在某些情況下,使用goto是有積極意義的。

一般寫程序時,認為goto有幾種用法是比較無害的。

首先保持程序的順序結構,就是goto是往下走,不會返回程序之前的地方。不影響程序的整體結構,但這種情況雖然無害,有的時候也是沒必要的,一般順序結構使用goto都是為了跳出某個loop,例如考慮下面的代碼:

for (int i = 0; i &< MAX_I; i++) {

// do something

goto outsideloop; // to break out of loop

}

outsideloop:

這裡無論多少層都可以這麼跳出。所以在大部分語言里完全可以通過break來實現跳出某個loop。

或者在多個loop裡面跳的時候,也可以使用state 和switch 去做。

還有可能就是在做異常處理的情況下,goto可能使用的比較普遍,但是大部分語言也提供了替代方案,例如在C裡面完全可以用setjmp()和longjmp()替代。

最後只有一個特別極限的狀態,可能會大量用到goto,就是finite-state machine,有興趣的話可以看看關於Ragel的東西:http://www.complang.org/ragel/

總的觀點就是:沒有必要的地方就不要用。


用得不好很容易把邏輯弄亂且難以理解


goto 最優秀的用途是避免在函數中出現多個 return。你看 Linux kernel 基本都是這種用法。

這對邏輯複雜但最後需要統一 clean-up 的代碼很需要。


本質就是goto的限制太弱了,限制越弱的東西,功能就越強,小範圍用起來越方便,但也越容易破壞工程約束和實踐。


主要還是代碼的可讀性,我們在閱讀代碼時,還是習慣於順序執行的代碼,如果行數很多,然後還看到了goto的蹤跡,也許多數人會覺得代碼不知道要跳到哪兒去了,於是便開罵了,它給人的感覺是:使代碼結構變得複雜的可能性大大增加了。與之相比,continue和break因為使代碼跳轉到較近的位置,於是比較容易被接受。但有些時候確實需要較大跨度的跳轉,比如多層循環等結構,這時我覺得也可以使用,不過這種情況貌似不會很多。


不用goto,請告訴我一種更好的寫法

if (init1()) {
printf("init1 failed
");
goto out_init1;
}

if (init2()) {
printf("init2 failed
");
goto out_init2;
}

if (init3()) {
printf("init3 failed
");
goto out_init3;
}

if (init4()) {
printf("init4 failed
");
goto out_init4;
}

if (init5()) {
printf("init5 failed
");
goto out_init5;
}

return 0;

out_init5:
destroy4();
out_init4:
destroy3();
out_init3:
destroy2();
out_init2:
destroy1();
out_init1:
return -1;


用do {} while(0); 代替


不是goto不是一種好的用法,而是沒用好goto後,容易讓代碼邏輯混亂。


其實也是有必要用的 如 goto exit


看了一下上面的評論,貌似說得很合理,因為大部分人都是這樣講的。真是傻了吧唧的。goto語句有害的本質原因是它違背了程序的局部性原理。因為這個原理,才使得計算機的設計者在設計計算機時可以使得計算機的運行速度提高數十倍。如果在代碼中使用了goto,就會使得那些為了提高計算機運行速度的設計都會作廢,這樣的損失是無法承擔的。所以不是講什麼goto語句使得代碼邏輯混亂,憑什麼跳一下就混亂了?那你怎麼不講while語句的循環執行好傻?


我的理解是,語言裡面的一些特性其實定位並不是給普通開發者使用的,而是給框架開發者使用的,對於普通開發者來說沒有用go也不會有什麼大問題而且代碼還好維護,而對於框架開發者來說沒有goto可能會有很多不方便的地方


你看代碼看到goto的時候

你並不能直觀地找到那個label在哪

這就很麻煩了


關注這問題很久了。

這問題從第一天就想不清。

邏輯混亂是主觀的

除錯困難我也沒覺得會成立

事實上程式碼也真的難讀了,

原因只在於變數的狀態要消耗太多精力去跟蹤了...

人腦在讀程式碼時其實也有很多優化來減少認知量,用多幾個goto, 讀起來就像每個變數都加了個volatile.


推薦閱讀:

如何用 C 語言畫一個「聖誕樹」?
如何評價C# 6的這個新特性?
怎樣減輕程序中 if 語句的依賴?
遊戲中的隨機地圖是如何保存的?
數據結構存儲數據內存不夠如何解決?

TAG:編程 | CC |