• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            隨筆-90  評(píng)論-947  文章-0  trackbacks-0

            原文:http://blog.csdn.net/pongba/archive/2007/10/08/1815742.aspx

            By 劉未鵬(pongba)
            C++的羅浮宮(http://blog.csdn.net/pongba)
            TopLanguage(http://groups.google.com/group/pongba)

            引言

            錯(cuò)誤處理(Error-Handling)這個(gè)重要議題從1997年(也許更早)到2004年左右一直是一個(gè)被廣泛爭(zhēng)論的話(huà)題,曾在新聞組上、博客上、論壇上引發(fā)口水無(wú)數(shù)(不亞于語(yǔ)言之爭(zhēng)),Bjarne Stroustrup、James Gosling、Anders Hejlsberg、Bruce Eckel、Joel Spolsky、Herb Sutter、Andrei Alexandrescu、Brad Abrams、Raymond Chen、David Abrahams…,各路神仙紛紛出動(dòng),好不熱鬧:-)

            如今爭(zhēng)論雖然已經(jīng)基本結(jié)束并結(jié)果;只不過(guò)結(jié)論散落在大量文獻(xiàn)當(dāng)中,且新舊文獻(xiàn)陳雜,如果隨便翻看其中的幾篇乃至幾十篇的話(huà)都難免管中窺豹。就連Gosling本人寫(xiě)的《The Java Programming Language》中也語(yǔ)焉不詳。所以,寫(xiě)這篇文章的目的便是要對(duì)這個(gè)問(wèn)題提供一個(gè)整體視圖,相信我,這是個(gè)有趣的話(huà)題:-)

            為什么要錯(cuò)誤處理?

            這是關(guān)于錯(cuò)誤處理的問(wèn)題里面最簡(jiǎn)單的一個(gè)。答案也很簡(jiǎn)單:現(xiàn)實(shí)世界是不完美的,意料之外的事情時(shí)有發(fā)生。

            一個(gè)現(xiàn)實(shí)項(xiàng)目不像在學(xué)校里面完成大作業(yè),只要考慮該完成的功能,走h(yuǎn)appy path(也叫One True Path)即可,忽略任何可能出錯(cuò)的因素(呃.. 你會(huì)說(shuō),怎么會(huì)出錯(cuò)呢?配置文件肯定在那,矩陣文件里面肯定含的是有效數(shù)字.. 因?yàn)樗械沫h(huán)境因素都在你的控制之下。就算出現(xiàn)什么不測(cè),比如運(yùn)行到一半被人拔了網(wǎng)線(xiàn),那就讓程序崩潰好了,再雙擊一下不就行了嘛)。

            然而現(xiàn)實(shí)世界的軟件就必須考慮錯(cuò)誤處理了。如果一個(gè)錯(cuò)誤是能夠恢復(fù)的,要盡量恢復(fù)。如果是不能恢復(fù)的,要妥善的退出模塊,保護(hù)用戶(hù)數(shù)據(jù),清理資源。如果有必要的話(huà)應(yīng)該記錄日志,或重啟模塊等等。

            簡(jiǎn)而言之,錯(cuò)誤處理的最主要目的是為了構(gòu)造健壯系統(tǒng)。

            什么時(shí)候做錯(cuò)誤處理?(或者:什么是“錯(cuò)誤”?)

            錯(cuò)誤,很簡(jiǎn)單,就是不正確的事情。也就是不該發(fā)生的事情。有一個(gè)很好的辦法可以找出哪些情況是錯(cuò)誤。首先就當(dāng)自己是在一個(gè)完美環(huán)境下編程的,一切precondition都滿(mǎn)足:文件存在那里,文件沒(méi)被破壞,網(wǎng)絡(luò)永遠(yuǎn)完好,數(shù)據(jù)包永遠(yuǎn)完整,程序員永遠(yuǎn)不會(huì)拿腦袋去打蒼蠅,等等… 這種情況下編寫(xiě)的程序也被稱(chēng)為happy path(或One True Path)。

            剩下的所有情況都可以看作是錯(cuò)誤。即“不應(yīng)該”發(fā)生的情況,不在算計(jì)之內(nèi)的情況,或者預(yù)料之外的情況,whatever。

            簡(jiǎn)而言之,什么錯(cuò)誤呢?調(diào)用方違反被調(diào)用函數(shù)的precondition、或一個(gè)函數(shù)無(wú)法維持其理應(yīng)維持的invariants、或一個(gè)函數(shù)無(wú)法滿(mǎn)足它所調(diào)用的其它函數(shù)的precondition、或一個(gè)函數(shù)無(wú)法保證其退出時(shí)的postcondition;以上所有情況都屬于錯(cuò)誤。(詳見(jiàn)《C++ Coding Standards: 101 Rules, Guidelines, and Best Practices》第70章,或《Object Oriented Software Construction, 2nd edition》第11、12章)

            例如文件找不到(通常意味著一個(gè)錯(cuò)誤)、配置文件語(yǔ)法錯(cuò)誤、將一個(gè)值賦給一個(gè)總應(yīng)該是正值的變量、文件存在但由于訪(fǎng)問(wèn)限制而不能打開(kāi),或打開(kāi)不能寫(xiě)、網(wǎng)絡(luò)傳輸錯(cuò)誤、網(wǎng)絡(luò)中斷、數(shù)據(jù)庫(kù)連接錯(cuò)誤、參數(shù)無(wú)效等。

            不過(guò)話(huà)說(shuō)回來(lái),現(xiàn)實(shí)世界中,錯(cuò)誤與非錯(cuò)誤之間的界定其實(shí)是很模糊的。例如文件缺失,可能大多數(shù)情況下都意味著一個(gè)錯(cuò)誤(影響程序正常執(zhí)行并得出正常結(jié)果),然而有的情況下也可能根本就不是錯(cuò)誤(或者至少是可恢復(fù)的錯(cuò)誤),比如你的街機(jī)模擬器的配置文件缺失了,一般程序只要?jiǎng)?chuàng)建一個(gè)缺省的即可。

            因此,關(guān)于把哪些情況界定為錯(cuò)誤,具體的答案幾乎總是視具體情況而定的。但話(huà)雖如此,仍然還是有一些一般性的原則,如下:

            哪些情況不屬于錯(cuò)誤?

            1.      控制程序流的返回值不是錯(cuò)誤:如果一個(gè)情況經(jīng)常發(fā)生,往往意味著它是用來(lái)控制程序流程的,應(yīng)該用status-code返回(注意,不同于error-code),比如經(jīng)典的while(cin >> i)。讀入整型失敗是很常見(jiàn)的情況,而且,這里的“讀入整型失敗”其實(shí)真正的含義是“流的下一個(gè)字段不是整型”,后者很明確地不代表一個(gè)錯(cuò)誤;再比如在一個(gè)字符串中尋找一個(gè)子串,如果找不到該子串,也不算錯(cuò)誤。這類(lèi)控制程序流的返回值都有一個(gè)共同的特點(diǎn),即我們都一定會(huì)利用它們的返回值來(lái)編寫(xiě)if-else,循環(huán)等控制結(jié)構(gòu),如:

              if(foo(…)) { … }
              else { … }

              while(foo(…)) { … }

            這里再摘兩個(gè)相應(yīng)的具體例子,一個(gè)來(lái)自Gosling的《The Java Programming Language》,是關(guān)于stream的。

            使用status-code:

              while ((token = stream.next()) != Stream.END)
                  process(token);
              stream.close();

            使用exception:

              try {
                for(;;) {
                  process(stream.next());
                }
              } catch (StreamEndException e) {
                stream.close();
              }

            高下立判。

            另一個(gè)例子來(lái)自TC++PL(Well, not exactly):

              size_t index;
              try {
                index = find(str, sub_str);
                … // case 1
              } catch (ElementNotFoundException& e) {
                … // case 2
              }

            使用status-code:

              int index = find(str, sub_str)
              if(index != -1) {
                … // case 1
              } else {
                … // case 2
              }

            以上這類(lèi)情況的特點(diǎn)是,返回值本身也是程序主邏輯(happy path)的一部分,返回的兩種或幾種可能性,都是完全正常的、預(yù)料之中的。

            1’. 另一方面,還有一種情況與此有微妙的區(qū)別,即“可恢復(fù)錯(cuò)誤”。可恢復(fù)錯(cuò)誤與上面的情況的區(qū)別在于它雖說(shuō)也是預(yù)料之中的,但它一旦發(fā)生程序往往就會(huì)轉(zhuǎn)入一個(gè)錯(cuò)誤恢復(fù)子過(guò)程,后者會(huì)盡可能恢復(fù)程序主干執(zhí)行所需要的某些條件,恢復(fù)成功程序則再次轉(zhuǎn)入主干執(zhí)行,而一旦恢復(fù)失敗的話(huà)就真的成了一個(gè)貨真價(jià)實(shí)的讓人只能干瞪眼的錯(cuò)誤了。比如C++里面的operator new如果失敗的話(huà)會(huì)嘗試調(diào)用一個(gè)可自定義的錯(cuò)誤恢復(fù)子過(guò)程,當(dāng)然,后者并非總能成功將程序恢復(fù)過(guò)來(lái)。除了轉(zhuǎn)入一個(gè)錯(cuò)誤恢復(fù)子過(guò)程之外,另一種可能性是程序會(huì)degenerate入一條second-class的支流,后者也許能完成某些預(yù)期的功能,但卻是“不完美”地完成的。

            這類(lèi)錯(cuò)誤如何處理后面會(huì)討論。

            2.      編程bug不是錯(cuò)誤。屬于同一個(gè)人維護(hù)的代碼,或者同一個(gè)小組維護(hù)的代碼,如果里面出現(xiàn)bug,使得一個(gè)函數(shù)的precondition得不到滿(mǎn)足,那么不應(yīng)該視為錯(cuò)誤。而應(yīng)該用assert來(lái)對(duì)付。因?yàn)榫幊蘠ug發(fā)生時(shí),你不會(huì)希望棧回滾,而是希望程序在assert失敗點(diǎn)上直接中斷,調(diào)用調(diào)試程序,查看中斷點(diǎn)的程序狀態(tài),從而解決代碼中的bug。

            關(guān)于這一點(diǎn),需要尤其注意的是,它的前提是:必須要是同一個(gè)人或小組維護(hù)的代碼。同一個(gè)小組內(nèi)可以保證查看到源代碼,進(jìn)行debug。如果調(diào)用方和被調(diào)用方不屬于同一負(fù)責(zé)人,則不能滿(mǎn)足precondition的話(huà)就應(yīng)該拋出異常。總之記住一個(gè)精神:assert是用來(lái)輔助debug的(assert的另一個(gè)作用是文檔,描述程序在特定點(diǎn)上的狀態(tài),即便assert被關(guān)閉,這個(gè)描述功能也依然很重要)。

            注意,有時(shí)候,為了效率原因,也會(huì)在第三方庫(kù)里面使用assert而不用異常來(lái)報(bào)告違反precondition。比如strcpy,std::vector的operator[]。

            3.      頻繁出現(xiàn)的不是錯(cuò)誤。頻繁出現(xiàn)的情況有兩種可能,一是你的程序問(wèn)題大了(不然怎么總是出錯(cuò)呢?)。二是出現(xiàn)的根本不是錯(cuò)誤,而是屬于程序的正常流程。后者應(yīng)該改用status-code。

            插曲:異常(exception)vs錯(cuò)誤代碼(error-code)

            異常相較于錯(cuò)誤代碼的優(yōu)勢(shì)太多了,以下是一個(gè)(不完全)列表。

            異常與錯(cuò)誤代碼的本質(zhì)區(qū)別之一——異常會(huì)自動(dòng)往上層棧傳播:一旦異常被拋出,執(zhí)行流就立即中止,取而代之的是自動(dòng)的stack-unwinding操作,直到找到一個(gè)適當(dāng)?shù)腸atch子句。

            相較之下,使用error-code的話(huà),要將下層調(diào)用發(fā)生的錯(cuò)誤傳播到上層去,就必須手動(dòng)檢查每個(gè)調(diào)用邊界,任何錯(cuò)誤,都必須手動(dòng)轉(zhuǎn)發(fā)(返回)給上層,稍有遺漏,就會(huì)帶著錯(cuò)誤的狀態(tài)繼續(xù)往下執(zhí)行,從而在下游不知道離了多遠(yuǎn)的地方最終引爆程序。這來(lái)了以下幾個(gè)問(wèn)題:

            1.      麻煩。每一個(gè)可能返回錯(cuò)誤代碼的調(diào)用邊界都需要檢查,不管你實(shí)際上對(duì)不對(duì)返回的錯(cuò)誤作響應(yīng),因?yàn)榧幢隳阕约翰唤鉀Q返回的錯(cuò)誤,也要把它傳播到上層去好讓上層解決。

            2.      不優(yōu)雅且不可伸縮(scalability)的代碼(錯(cuò)誤處理代碼跟One True Path(也叫happy path)攪和在一起)。關(guān)于這一條普遍的論述都不夠明確,比如有人可能會(huì)反駁說(shuō),那錯(cuò)誤反正是要檢查的,用異常難道就不需要捕獲異常了嗎?當(dāng)然是需要的,但關(guān)鍵是,有時(shí)候我們不一定會(huì)在異常發(fā)生的立即點(diǎn)上捕獲并處理異常。這時(shí)候,異常的優(yōu)勢(shì)就顯現(xiàn)出來(lái)了,比如:

              void foo()
              {
                try {
                  op1;
                  op2;
                  …
                  opN;
                } catch (…) {
                  … // log
                  … // clean up
                  throw;
                }
              }

            如果用error-code的話(huà):

              int foo()
              {
                if(!op1()) {
                  … // log? clean up?
                  return FAILED;
                }
                if(!op2()) {
                  … // log? clean up?
                  return FAILED;
                }
                …
                return SUCCEEDED;
              }

            好一點(diǎn)的是這樣:

              int foo()
              {
                if(!op1()) goto FAILED;
                if(!op2()) goto FAILED;
                …
                if(!opN()) goto FAILED;
                return SUCCEEDED;
              FAILED:
                … // log, clean up
                return FAILED;
              }

            就算是最后一種做法(所謂的“On Error Goto”),One True Path中仍然夾雜著大量的噪音(如果返回的錯(cuò)誤值不只是FAILED/SUCCEEDED兩種的話(huà)噪音會(huì)更大)。此外手動(dòng)檢查返回值的成功失敗畢竟是很error-prone的做法。

            值得注意的是,這里我并沒(méi)有用一個(gè)常被引用的例子,即:如果你是用C寫(xiě)代碼(C不支持局部變量自動(dòng)析構(gòu)(RAII)),那么程序往往會(huì)被寫(xiě)成這樣:

              int f()
              {
                int returnCode = FAILED;
                acquire resource1;
                if(resource1 is acquired) {
                  acquire resource2;
                  if(resource2 is acquired) {
                    acquire resource3;
                    if(resource3 is acquired) {
                      if(doSomething1()) {
                        if(doSomething2()) {
                          if(doSomething3()) {
                            returnCode = SUCCEEDED;
                          }
                        }
                      }
                      release resource3;
                    }
                    release resource2;
                  }
                  release resource1;
                }
                return returnCode;
              }

            或者像這樣:

              int f()
              {
                int returnCode = FAILED;
                acquire resource1;
                if(resources1 is not acquired)
                  return FAILED;
                acquire resource2;
                if(resource2 is not acquired) {
                  release resource1;
                  return FAILED;
                }
                acquire resource3;
                if(resource3 is not acquired) {
                  release resource2;
                  release resource1;
                  return FAILED;
                }
                ... // do something
                release resource3;
                release resource2;
                release resource1;
                return SUCCEEDED;
              }

            (一個(gè)更復(fù)雜的具體例子可以參考[16])

            以上兩種方案在可伸縮性方面的問(wèn)題是顯而易見(jiàn)的:一旦需要獲取的資源多了以后代碼也會(huì)隨著越來(lái)越難以卒讀,要么是if嵌套層次隨之線(xiàn)性增多,要么是重復(fù)代碼增多。所以即便項(xiàng)目中因?yàn)槟承┈F(xiàn)實(shí)原因只能使用error-code,也最好采用前面提到的“On Error Goto”方案。

            另一方面,當(dāng)整個(gè)函數(shù)需要保持異常中立的時(shí)候,異常的優(yōu)勢(shì)就更顯現(xiàn)出來(lái)了:使用error-code,你還是需要一次次的小心check每個(gè)返回的錯(cuò)誤值,從而阻止執(zhí)行流帶著錯(cuò)誤往下繼續(xù)執(zhí)行。用異常的話(huà),可以直接書(shū)寫(xiě)One True Path,連try-catch都不要。

            當(dāng)然,即便是使用異常作為錯(cuò)誤匯報(bào)機(jī)制,錯(cuò)誤安全(error-safety)還是需要保證的。值得注意的是,錯(cuò)誤安全性屬于錯(cuò)誤處理的本質(zhì)困難,跟使用異常還是error-code來(lái)匯報(bào)錯(cuò)誤沒(méi)有關(guān)系,一個(gè)常見(jiàn)的謬誤就是許多人把在異常使用過(guò)程中遇到的錯(cuò)誤安全性方面的困難歸咎到異常身上。

            3.      脆弱(易錯(cuò))。只要忘了檢查任意一個(gè)錯(cuò)誤代碼,執(zhí)行流就必然會(huì)帶著錯(cuò)誤狀態(tài)往下繼續(xù)執(zhí)行,后者幾乎肯定不是你想要的。帶著錯(cuò)誤狀態(tài)往下執(zhí)行好一點(diǎn)的會(huì)立即崩潰,差一點(diǎn)的則在相差十萬(wàn)八千里的地方引發(fā)一個(gè)莫名其妙的錯(cuò)誤。

            4.      難以(編寫(xiě)時(shí))確保和(review時(shí))檢查代碼的正確性。需要檢查所有可能的錯(cuò)誤代碼有沒(méi)有都被妥善check了,其中也許大部分都是不能直接對(duì)付而需要傳播給上級(jí)的錯(cuò)誤。

            5.      耦合。即便你的函數(shù)是一個(gè)異常中立的函數(shù),不管底層傳上來(lái)哪些錯(cuò)誤一律拋給上層,你仍然需要在每個(gè)調(diào)用的邊界檢查,并妥善往上手動(dòng)傳播每一個(gè)錯(cuò)誤代碼。而一旦底層接口增加、減少或改動(dòng)錯(cuò)誤代碼的話(huà),你的函數(shù)就需要立即作出相應(yīng)改動(dòng),檢查并傳播底層接口改動(dòng)后相應(yīng)的錯(cuò)誤代碼——這是很不幸的,因?yàn)槟愕暮瘮?shù)只是想保持異常中立,不管底層出什么錯(cuò)一律拋給上層調(diào)用方,這種情況下理想情況應(yīng)該是不管底層的錯(cuò)誤語(yǔ)意如何修改,當(dāng)前層都應(yīng)該不需要改動(dòng)才對(duì)。

            6.      沒(méi)有異常,根本無(wú)法編寫(xiě)泛型組件。泛型組件根本不知道底層會(huì)出哪些錯(cuò),泛型組件的特點(diǎn)之一便是錯(cuò)誤中立。但用error-code的話(huà),怎么做到錯(cuò)誤中立?泛型組件該如何檢查,檢查哪些底層錯(cuò)誤?唯一的辦法就是讓所有的底層錯(cuò)誤都用統(tǒng)一的SUCCEEDED和FAILED代碼來(lái)表達(dá),并且將其它錯(cuò)誤信息用GetLastError來(lái)獲取。姑且不說(shuō)這個(gè)方案的丑陋,如何、由誰(shuí)來(lái)統(tǒng)一制定SUCCEEDED和FAILED、GetLastError的標(biāo)準(zhǔn)?就算有這個(gè)統(tǒng)一標(biāo)準(zhǔn),你也可以設(shè)想一下某個(gè)標(biāo)準(zhǔn)庫(kù)泛型算法(如for_each)編寫(xiě)起來(lái)該是如何丑陋。

            7.      錯(cuò)誤代碼不可以被忘掉(忽視)。忘掉的后果見(jiàn)第3條。此外,有時(shí)候我們可能會(huì)故意不管某些錯(cuò)誤,并用一個(gè)萬(wàn)能catch來(lái)捕獲所有未被捕獲的錯(cuò)誤,log,向支持網(wǎng)站發(fā)送錯(cuò)誤報(bào)告,并重啟程序。用異常這就很容易做到——只要寫(xiě)一個(gè)unhandled exception handler(不同語(yǔ)言對(duì)此的支持機(jī)制不一樣)即可。

            異常與錯(cuò)誤代碼的本質(zhì)區(qū)別之二——異常的傳播使用的是一個(gè)單獨(dú)的信道,而錯(cuò)誤代碼則占用了函數(shù)的返回值;函數(shù)的返回值本來(lái)的語(yǔ)意是用來(lái)返回“有用的”結(jié)果的,這個(gè)結(jié)果是屬于程序的One True Path的,而不是用來(lái)返回錯(cuò)誤的。

            利用返回值來(lái)傳播錯(cuò)誤導(dǎo)致的問(wèn)題如下:

            8.      所有函數(shù)都必須將返回值預(yù)留給錯(cuò)誤。如果你的函數(shù)最自然的語(yǔ)意是返回一個(gè)double,而每個(gè)double都是有效的。不行,你得把這個(gè)返回值通道預(yù)留著給錯(cuò)誤處理用。你可能會(huì)說(shuō),我的函數(shù)很簡(jiǎn)單,不會(huì)出錯(cuò)。但如果以后你修改了之后,函數(shù)復(fù)雜了呢?到那個(gè)時(shí)候再把返回的double改為int并加上一個(gè)double&作為out參數(shù)的話(huà),改動(dòng)可就大了。

            9.      返回值所能承載的錯(cuò)誤信息是有限的。NULL?-1?什么意思?具體的錯(cuò)誤信息只能用GetLastError來(lái)提供… 哦,對(duì)了,你看見(jiàn)有多少人用過(guò)GetLastError的?

            10.  不優(yōu)雅的代碼。呃…這個(gè)問(wèn)題前面不是說(shuō)過(guò)了么?不,這里說(shuō)的是另一個(gè)不優(yōu)雅之處——占用了用來(lái)返回結(jié)果的返回值通道。本來(lái)很自然的“計(jì)算——返回結(jié)果”,變成了“計(jì)算——修改out參數(shù)——返回錯(cuò)誤”。當(dāng)然,你可以說(shuō)這個(gè)問(wèn)題不是很?chē)?yán)重。的確,將double res = readInput();改為double res; readInput(&res);也沒(méi)什么大不了的。如果是連調(diào)用呢?比如,process(readInput());呃… 或者readInput() + …?或者一般地,op1(op2(), op3(), …);?

            11.  錯(cuò)誤匯報(bào)方案不一致性。看看Win32下面的錯(cuò)誤匯報(bào)機(jī)制吧:HRESULT、BOOL、GetLastError…本質(zhì)上就是因?yàn)槔梅祷刂低ǖ朗且粋€(gè)補(bǔ)丁方案,錯(cuò)誤處理是程序的一個(gè)方面(aspect),理應(yīng)有其單獨(dú)的匯報(bào)通道。利用異常的話(huà),錯(cuò)誤匯報(bào)方案就立即統(tǒng)一了,因?yàn)檫@是一個(gè)first-class的語(yǔ)言級(jí)支持機(jī)制。

            12.  有些函數(shù)根本無(wú)法返回值,如構(gòu)造函數(shù)。有些函數(shù)返回值是語(yǔ)言限制好了的,如重載的操作符和隱式轉(zhuǎn)換函數(shù)。

            異常與錯(cuò)誤代碼的本質(zhì)區(qū)別之三——異常本身能夠攜帶任意豐富的信息

            13.  有什么錯(cuò)誤報(bào)告機(jī)制能比錯(cuò)誤報(bào)告本身就包含盡量豐富的信息更好的呢?使用異常的話(huà),你可以往異常類(lèi)里面添加數(shù)據(jù)成員,添加成員函數(shù),攜帶任意的信息(比如Java的異常類(lèi)就缺省攜帶了非常有用的調(diào)用棧信息)。而錯(cuò)誤代碼就只有一個(gè)單薄的數(shù)字或字符串,要攜帶其它信息只能另外存在其它地方,并期望你能通過(guò)GetLastError去查看。

            異常與錯(cuò)誤代碼的本質(zhì)區(qū)別之四——異常是OO的

            14.  你可以設(shè)計(jì)自己的異常繼承體系。呃…那這又有什么用呢?當(dāng)然有用,一個(gè)最大的好處就是你可以在任意抽象層次上catch一組異常(exception grouping),比如你可以用catch(IOException)來(lái)捕獲所有的IO異常,用catch(SQLException)來(lái)捕獲所有的SQL異常。用catch(FileException)來(lái)catch所有的文件異常。你也可以catch更明確一點(diǎn)的異常,如StreamEndException。總之,catch的粒度是粗是細(xì),根據(jù)需要,隨你調(diào)節(jié)。當(dāng)然了,你可以設(shè)計(jì)自己的新異常。能夠catch一組相關(guān)異常的好處就是你可以很方便的對(duì)他們做統(tǒng)一的處理。

            異常與錯(cuò)誤代碼的本質(zhì)區(qū)別之五——異常是強(qiáng)類(lèi)型的

            15.  異常是強(qiáng)類(lèi)型的。在catch異常的時(shí)候,一個(gè)特定類(lèi)型的catch只能catch類(lèi)型匹配的異常。而用error-code的話(huà),就跟enum一樣,類(lèi)型不安全。-1 == foo()?FAILED == foo()?MB_OK == foo()?大家反正都是整數(shù)。

            異常與錯(cuò)誤代碼的本質(zhì)區(qū)別之六——異常是first-class的語(yǔ)言機(jī)制

            16.  代碼分析工具可以識(shí)別出異常并進(jìn)行各種監(jiān)測(cè)或分析。比如PerfMon就會(huì)對(duì)程序中的異常做統(tǒng)計(jì)。這個(gè)好處放在未來(lái)時(shí)態(tài)下或許怎么都不應(yīng)該小覷。

            選擇什么錯(cuò)誤處理機(jī)制?

            看完上面的比較,答案相信應(yīng)該已經(jīng)很明顯了:異常。

            如果你仍然是error-code的思維習(xí)慣的話(huà),可以假想將所有error-code的地方改為拋出exception。需要注意的是,error-code不是status-code。并非所有返回值都是用來(lái)報(bào)告真正的錯(cuò)誤的,有些只不過(guò)是控制程序流的。就算返回的是bool值(比如查找子串,返回是否查找到),也并不代表false的情況就是一個(gè)錯(cuò)誤。具體參加上一節(jié):“哪些情況不屬于錯(cuò)誤”。

            一個(gè)最為廣泛的誤解就是:異常引入了不菲的開(kāi)銷(xiāo),而error-code沒(méi)有開(kāi)銷(xiāo),所以應(yīng)該使用error-code。這個(gè)論點(diǎn)的漏洞在于,它認(rèn)為只要是開(kāi)銷(xiāo)就是有問(wèn)題的,而不關(guān)心到底是在什么情況下的開(kāi)銷(xiāo)。實(shí)際上,現(xiàn)代的編譯器早已能夠做到異常在happy path上的零開(kāi)銷(xiāo)。當(dāng)然,空間開(kāi)銷(xiāo)還是有的,因?yàn)榱汩_(kāi)銷(xiāo)方案用的是地址表方案;但相較于時(shí)間開(kāi)銷(xiāo),這里的空間開(kāi)銷(xiāo)幾乎從來(lái)都不是個(gè)問(wèn)題。另一方面,一旦發(fā)生了異常,程序肯定就出了問(wèn)題,這個(gè)時(shí)候的時(shí)間開(kāi)銷(xiāo)往往就不那么重要了。此外有人會(huì)說(shuō),那如果頻繁拋出異常呢?如果頻繁拋出異常,往往就意味著那個(gè)異常對(duì)應(yīng)的并非一個(gè)錯(cuò)誤情況。

            《C++ Coding Standards: 101 Rules, Guidelines, and Best Practices》里面一再?gòu)?qiáng)調(diào):不要在項(xiàng)目里面關(guān)閉異常支持。因?yàn)榫退隳愕捻?xiàng)目里面不拋出異常,標(biāo)準(zhǔn)庫(kù)也依賴(lài)于異常。一旦關(guān)閉異常,不僅你的項(xiàng)目代碼都要依賴(lài)于error-code(error-code的缺點(diǎn)見(jiàn)下一節(jié)),整個(gè)標(biāo)準(zhǔn)庫(kù)便也都要依賴(lài)于非標(biāo)準(zhǔn)的途徑來(lái)匯報(bào)錯(cuò)誤,或者干脆就不匯報(bào)錯(cuò)誤。如果你的項(xiàng)目是如此的硬實(shí)時(shí),乃至于你在非常小心且深入的分析之后發(fā)覺(jué)某些操作真的負(fù)擔(dān)不起異常些微的空間開(kāi)銷(xiāo)和unhappy path上的時(shí)間開(kāi)銷(xiāo)的話(huà),也要盡量別在全局關(guān)閉異常支持,而是盡量將這些敏感的操作集中到一個(gè)模塊中,按模塊關(guān)閉異常。

            插曲:異常的例外情況

            凡事都有例外。《C++ Coding Standards: 101 Rules, Guidelines, and Best Practices》上面陳述了兩個(gè)例外情況:一,用異常沒(méi)有帶來(lái)明顯的好處的時(shí)候:比如所有的錯(cuò)誤都會(huì)在立即調(diào)用端解決掉或者在非常接近立即調(diào)用端的地方解決掉。二,在實(shí)際作了測(cè)定之后發(fā)現(xiàn)異常的拋出和捕獲導(dǎo)致了顯著的時(shí)間開(kāi)銷(xiāo):這通常只有兩種情況,要么是在內(nèi)層循環(huán)里面,要么是因?yàn)楸粧伋龅漠惓8静粚?duì)應(yīng)于一個(gè)錯(cuò)誤。

            如何進(jìn)行錯(cuò)誤處理?

            這個(gè)問(wèn)題同樣極其重要。它分為三個(gè)子問(wèn)題:

            1.      何時(shí)拋出異常。

            2.      何時(shí)捕獲異常。

            3.      如何避開(kāi)異常,保持異常中立(或“異常透明”)。

            其中最后一個(gè)問(wèn)題最為重要,屬于錯(cuò)誤處理的本質(zhì)性困難之一。

            先說(shuō)前兩個(gè)問(wèn)題。

            從本質(zhì)上,錯(cuò)誤分為兩種,一種是可恢復(fù)的,另一種是不可恢復(fù)的。

            對(duì)于可恢復(fù)的錯(cuò)誤。有兩種方案:

            1.      在錯(cuò)誤的發(fā)生點(diǎn)上立即就予以恢復(fù)。比如配置文件不存在便創(chuàng)建一個(gè)缺省的,某個(gè)配置項(xiàng)缺失就使用缺省值等等。這一方案的好處是當(dāng)前函數(shù)不返回任何錯(cuò)誤,因?yàn)殄e(cuò)誤被當(dāng)即搞定了,就像沒(méi)發(fā)生一樣。這種方案的前提是當(dāng)前函數(shù)必須要有對(duì)付該錯(cuò)誤的足夠上下文,如果一個(gè)底層的函數(shù)對(duì)全局語(yǔ)意沒(méi)有足夠的視圖,這時(shí)就可以?huà)伋霎惓#缮蠈雍瘮?shù)負(fù)責(zé)恢復(fù)。

            2.      在某個(gè)上層棧上恢復(fù)。這種情況下,在負(fù)責(zé)恢復(fù)的那層棧以下的調(diào)用一般被看成一個(gè)整體事務(wù),其中發(fā)生的任何錯(cuò)誤都導(dǎo)致整個(gè)事務(wù)回滾,回滾到錯(cuò)誤恢復(fù)棧層面時(shí),由相應(yīng)的catch子句進(jìn)行恢復(fù),并重新執(zhí)行整個(gè)事務(wù),或者將程序引向另一條備選路徑(alternative)。

            對(duì)于不可恢復(fù)的錯(cuò)誤。也有兩種方案:

            1.      Sudden Death。在錯(cuò)誤的發(fā)生點(diǎn)上退出模塊(可能伴隨著重啟模塊)。退出模塊前往往需要先釋放資源、保存關(guān)鍵數(shù)據(jù)、記錄日志,等等。該方案的前提是在錯(cuò)誤的發(fā)生點(diǎn)的上下文中必須要能夠釋放所有資源,要能夠保存關(guān)鍵數(shù)據(jù)。要滿(mǎn)足這個(gè)前提,可以用一個(gè)全局的沙盒來(lái)保存整個(gè)模塊到當(dāng)前為止申請(qǐng)的所有資源,從而在任何出錯(cuò)點(diǎn)上都可以將這個(gè)沙盒整個(gè)釋放掉。也可以用智能垃圾收集,這樣在出錯(cuò)點(diǎn)上只要記錄日志和保存數(shù)據(jù),把掃尾工作留給智能垃圾收集器完成。這個(gè)方案的弱點(diǎn)是如果釋放資源是要按某種次序的就比較麻煩。

            2.      回滾。如果你并沒(méi)有用智能垃圾收集(要智能到能夠回收文件句柄,網(wǎng)絡(luò)端口等,不光是內(nèi)存),或者你并沒(méi)有在某個(gè)全局可訪(fǎng)問(wèn)的位置保存到當(dāng)前為止模塊申請(qǐng)的所有資源,或者你的資源互相之間有依賴(lài)關(guān)系,必須按照分配的逆序釋放,等等,那么就必須按照調(diào)用棧的反方向回滾事務(wù)。回滾到一個(gè)所謂的Fault Barrier,用一個(gè)catch-all在那里等著,所謂Fault-Barrier的作用就是為了抓這些沒(méi)法妥善恢復(fù)的錯(cuò)誤的,它做的事情通常就是logging、發(fā)送錯(cuò)誤報(bào)告、可能也會(huì)重啟模塊。Fault Barrier一般在一個(gè)內(nèi)聚的單一職責(zé)的功能模塊的邊界出現(xiàn)。

            嚴(yán)格來(lái)說(shuō),其實(shí)還有第三種情況,即編寫(xiě)當(dāng)前代碼的時(shí)候并不確定某個(gè)錯(cuò)誤是否能被恢復(fù)。這種時(shí)候拋出一個(gè)異常往往是最靈活的選擇,因?yàn)樵跊](méi)有想好恢復(fù)方案的時(shí)候,上層調(diào)用對(duì)該異常都是中立的。一旦后面想好恢復(fù)方案了,不管是在某個(gè)上層調(diào)用內(nèi)捕獲該異常,還是在最底層錯(cuò)誤發(fā)生點(diǎn)上就立即解決錯(cuò)誤從而根本取消掉該異常,都沒(méi)有問(wèn)題。

            異常轉(zhuǎn)換

            在如何拋出和捕獲異常的問(wèn)題上,還有一個(gè)子問(wèn)題就是異常的轉(zhuǎn)換(translation)。以下情況下應(yīng)該轉(zhuǎn)換一個(gè)由底層傳上來(lái)的異常:

            1.      拋出一個(gè)對(duì)應(yīng)于當(dāng)前抽象層的異常。比如Document::open當(dāng)接收到底層的文件異常(或數(shù)據(jù)庫(kù)異常,網(wǎng)絡(luò)異常,取決于這個(gè)Document來(lái)自何方)時(shí),將其轉(zhuǎn)換為“文檔無(wú)效或被破壞”異常,增加高層語(yǔ)意,并避免暴露底層實(shí)現(xiàn)細(xì)節(jié)(對(duì)異常的一個(gè)批評(píng)就是會(huì)暴露內(nèi)部實(shí)現(xiàn),而實(shí)際上,通過(guò)適當(dāng)轉(zhuǎn)換異常,可以使得異常總位于當(dāng)前的抽象層次上,成為接口的一部分)。

            2.      在模塊邊界上。如果一個(gè)模塊,在內(nèi)部使用異常,但在邊界上必須提供C API的話(huà),就必須在邊界上捕獲異常并將其轉(zhuǎn)換為error-code。

            沒(méi)有時(shí)間機(jī)器——錯(cuò)誤處理的本質(zhì)困難

            剛才提到“回滾”。那么,在異常發(fā)生的時(shí)候,如何回滾既然已經(jīng)發(fā)生的操作?這就是要說(shuō)的第三個(gè)問(wèn)題:如何在異常從發(fā)生點(diǎn)一路傳播到捕獲點(diǎn)的路徑上保持異常中立,即回滾操作,釋放資源。簡(jiǎn)而言之就是要做到錯(cuò)誤安全(error-safe)。錯(cuò)誤安全攸關(guān)強(qiáng)異常安全保證和事務(wù)語(yǔ)意。在異常編程里面,錯(cuò)誤安全是最重要的一環(huán)。

            理想情況下,我們要的是一個(gè)時(shí)間機(jī)器:打碎的杯子要能還原,釋放的內(nèi)存要能重新得到,銷(xiāo)毀的對(duì)象就像沒(méi)銷(xiāo)毀前一模一樣,發(fā)射的導(dǎo)彈就像從來(lái)也沒(méi)有離開(kāi)發(fā)射筒一樣,數(shù)據(jù)庫(kù)就像從來(lái)沒(méi)被寫(xiě)入一樣…

            沒(méi)有時(shí)間機(jī)器。

            那么,如何回滾木已成舟的操作?

            目前有兩個(gè)主要方案:

            1.      Discard(丟棄):一個(gè)例子就能夠說(shuō)明這種做法,源自STL的“copy-swap手法”。比如一個(gè)vector,你要往里面插入一個(gè)元素,如果插入元素失敗的話(huà)你想要vector維持原狀,就好像從來(lái)沒(méi)有動(dòng)過(guò)一樣。如何做到這一點(diǎn)呢?你可以先把這個(gè)vector拷貝一份,往拷貝里面插入元素,然后將兩個(gè)vector調(diào)換(swap)一下即可,swap是不會(huì)失敗的,因?yàn)樗皇前褍蓚€(gè)指針互換了一下。而如果往那個(gè)拷貝里面插入元素失敗的話(huà),拷貝就會(huì)被Discard(丟棄掉),不會(huì)帶來(lái)任何實(shí)際的副作用。當(dāng)然,這種做法是有代價(jià)的,誰(shuí)叫你要強(qiáng)異常安全保證呢?再比如一個(gè)拷貝賦值操作符可以這樣寫(xiě):MyClass(other).swap(*this); 當(dāng)然,前提還是swap()必須具有標(biāo)準(zhǔn)的no-throw語(yǔ)意。

            這種做法一般化的描述就是:“在一個(gè)‘副本’里把所有的事情都做好了,然后用一個(gè)不會(huì)出錯(cuò)的函數(shù)提交(commit)”。這樣一來(lái),中途出了任何錯(cuò)誤只要丟棄那個(gè)副本即可(往往只要任其析構(gòu))。要做到這一點(diǎn),一個(gè)原則就是:“在破壞一份信息之前要確保其新的版本一定能夠無(wú)錯(cuò)的替換掉原信息”,例如在拷貝構(gòu)造函數(shù)中,不能先delete再new,因?yàn)閚ew可能失敗,一旦new失敗了,delete掉的信息可就找不回來(lái)了。

            2.      Undo(撤銷(xiāo)):有時(shí)候,你一方面不想付出Discard方案的(通常不菲的)空間開(kāi)銷(xiāo),另一方面你又想擁有強(qiáng)異常安全保證。這也是有辦法的,比如:

              void World::addPerson(Person const& person)
              {
                m_persons.push_back(person);
                scope(failure) { m_persons.pop_back(); }
                … // other operations
              }

            scope(failure)是D語(yǔ)言的特性,其語(yǔ)意顯而易見(jiàn):如果當(dāng)前的scope以失敗(異常)退出的話(huà),{}內(nèi)的語(yǔ)句就被執(zhí)行。這么一來(lái),在上面的例子中,如果后續(xù)的操作失敗,那么這個(gè)person就會(huì)被從m_persons中pop_back出來(lái),一次事務(wù)撤銷(xiāo),使得這個(gè)函數(shù)對(duì)m_persons的影響歸零。

            該方案的前提有兩個(gè):一,回滾操作(比如這里的m_persons.pop_back())必須存在且不會(huì)失敗(no-throw)。比如missile.lunch()就不能回滾,操作系統(tǒng)API一般也無(wú)法回滾;I/O操作也無(wú)法回滾;另一方面,內(nèi)存操作一般而言都是可以回滾的,只要回復(fù)原來(lái)內(nèi)存的值即可。二,被回滾的操作(比如這里的m_persons.push_back(person))也一定要是強(qiáng)異常保證的。比如在中間而不是尾部插入一個(gè)person(m_persons.insert(iter, person))就不是強(qiáng)保證的,這種時(shí)候就要訴諸前一個(gè)方案(Discard)。

            D的scope(failure)(還有scope(exit)、scope(success))是非常強(qiáng)大的設(shè)施。利用它,一個(gè)具有事務(wù)語(yǔ)意的函數(shù)的一般模式如下:

              void Transaction()
              {
                op1; // strong operation
                scope(failure) { undo op1; }
                op2; // strong operation
                scope(failure) { undo op2; }
                …
                opN; // strong operation
                scope(failure) { undo opN; }
              }

            在C++里面也可以模擬D的scope(failure),Andrei Alexandrescu曾實(shí)現(xiàn)了一個(gè)ScopeGuard類(lèi)[11],而旨在完全模擬D的scope特性的boost.scope-exit也在review中。只不過(guò)C++03里面的模擬方案有一些學(xué)習(xí)曲線(xiàn)和使用注意點(diǎn),C++09之后會(huì)有更方便的方案。在其它不支持scope(failure)的語(yǔ)言中,也可以模擬這種做法,不過(guò)做法很笨拙。

            3.      呃… 哪來(lái)的第三個(gè)方案?前面不是說(shuō)了只有兩個(gè)方案嗎?是的。因?yàn)檫@第三個(gè)方案是“理想”方案,目前還沒(méi)有進(jìn)入主流語(yǔ)言,不過(guò)在haskell里面已經(jīng)初見(jiàn)端倪了。強(qiáng)異常安全保證的核心思想其實(shí)就是事務(wù)語(yǔ)意,而事務(wù)語(yǔ)意的核心思想就是“不成功便成仁”(這個(gè)思想有許多有趣的說(shuō)法,比如:“武士道原則”、“要么直著回來(lái)要么橫著回來(lái)”、“干不了就去死”),根據(jù)這個(gè)想法,其實(shí)最簡(jiǎn)單的方案是把一組屬于同一事務(wù)的操作簡(jiǎn)單地圈起來(lái)(標(biāo)記出來(lái)),把回滾操作留給語(yǔ)言實(shí)現(xiàn)去完成:

              stm {
                op1;
                op2;
                …
                opN;
              }

            只要op1至opN中任意一個(gè)失敗,整個(gè)stm塊對(duì)內(nèi)存的寫(xiě)操作就全被自動(dòng)拋棄(這要求編譯器和運(yùn)行時(shí)的支持,一般是用一個(gè)緩沖區(qū)或?qū)懭罩緛?lái)實(shí)現(xiàn)),然后異常被自動(dòng)拋出這個(gè)stm塊之外。這個(gè)方案的優(yōu)點(diǎn)是它太優(yōu)美了,我們幾乎只要關(guān)注One True Path即可,唯一要做的事情就是用stm{…}圈出代碼中需要強(qiáng)保證的代碼段。這個(gè)方案的缺點(diǎn)有兩個(gè):一,它跟第一個(gè)方案一樣,有空間開(kāi)銷(xiāo),不過(guò)空間開(kāi)銷(xiāo)通常要小一點(diǎn),因?yàn)橹灰彺嫣囟ǖ膶?xiě)操作。二,當(dāng)涉及到操作系統(tǒng)API,I/O等“外部”操作的時(shí)候,底層實(shí)現(xiàn)就未必能夠回滾這些操作了。另一個(gè)理論上的可能性是,當(dāng)回滾操作和被回滾操作并非嚴(yán)格物理對(duì)應(yīng)(所謂物理對(duì)應(yīng)就是說(shuō),回滾操作將內(nèi)存回滾到目標(biāo)操作發(fā)生之前的狀態(tài))的時(shí)候,底層實(shí)現(xiàn)也不知道如何回滾。

            (STM(軟件事務(wù)內(nèi)存)目前在haskell里面實(shí)現(xiàn)了,Intel也釋出了C/C++的STM預(yù)覽版編譯器。只不過(guò)STM原本的意圖是實(shí)現(xiàn)鎖無(wú)關(guān)算法的。后者就是另一個(gè)話(huà)題了。)

            RAII

            實(shí)際上剛才還有一個(gè)問(wèn)題沒(méi)有說(shuō),那就是如何確保資源一定會(huì)被釋放(即便發(fā)生異常),這在D里面對(duì)應(yīng)的是scope(exit),在Java里面對(duì)應(yīng)的是finally,在C#   里面對(duì)應(yīng)的是scoped using。

            簡(jiǎn)而言之就是,不管當(dāng)前作用域以何種方式退出,某某操作(通常是資源釋放)都一定要被執(zhí)行。

            這個(gè)問(wèn)題的答案其實(shí)C++程序員們應(yīng)該耳熟能詳了:RAII。RAII是C++最為強(qiáng)大的特性之一。在C++里面,局部變量的析構(gòu)函數(shù)剛好滿(mǎn)足這個(gè)語(yǔ)意:無(wú)論當(dāng)前作用域以何種方式退出,所有局部變量的析構(gòu)函數(shù)都必然會(huì)被倒著調(diào)用一遍。所以只要將有待釋放的資源包裝在析構(gòu)函數(shù)里面,就能夠保證它們即便在異常發(fā)生的情況下也會(huì)被釋放掉了。為此C++提供了一系列的智能指針:auto_ptr、scoped_ptr、scoped_array… 此外所有的STL容器也都是RAII的。在C++里面模擬D的scope(exit)也是利用的RAII。

            RAII相較于java的finally的好處和C#的scoped using的好處是非常明顯的。只要一段代碼就高下立判:

              // in Java
              String ReadFirstLineFromFile( String path )
              {
                StreamReader r = null;
                String s = null;
                try {
                  r = new StreamReader(path);
                  s = r.ReadLine();
                } finally {
                  if ( r != null ) r.Dispose();
                }
                return s;
              }

              // in C#
              String ReadFirstLineFromFile( String path )
              {
                using ( StreamReader r = new StreamReader(path) ) {
                  return r.ReadLine();
                }
              }

            顯然,Java版本的(try-finally)最臃腫。C#版本(scoped using)稍微好一些,但using畢竟也不屬于程序員關(guān)心的代碼邏輯,仍然屬于代碼噪音;況且如果不連續(xù)地申請(qǐng)N個(gè)資源的話(huà),使用using就會(huì)造成層層嵌套結(jié)構(gòu)。

            如果使用RAII手法來(lái)封裝StreamReader類(lèi)的話(huà)(std::fstream就是RAII類(lèi)的一個(gè)范例),代碼就簡(jiǎn)化為:

              // in C++, using RAII
              String ReadFirstLineFromFile(String path)
              {
                StreamReader r(path);
                return r.ReadLine();
              }

            好處是顯而易見(jiàn)的。完全不用擔(dān)心資源的釋放問(wèn)題,代碼也變得“as simple as possible”。此外,值得注意的是,以上代碼只是演示了最簡(jiǎn)單的情況,其中需要釋放的資源只有一個(gè)。其實(shí)這個(gè)例子并不能最明顯地展現(xiàn)出RAII強(qiáng)大的地方,當(dāng)需要釋放的資源有多個(gè)的時(shí)候,RAII的真正強(qiáng)大之處才被展現(xiàn)出來(lái),一般地說(shuō),如果一個(gè)函數(shù)依次申請(qǐng)N個(gè)資源:

              void f()
              {
                acquire resource1;
                …
                acquire resource2;
                …
                acquire resourceN;
                …
              }

            那么,使用RAII的話(huà),代碼就像上面這樣簡(jiǎn)單。無(wú)論何時(shí)退出當(dāng)前作用域,所有已經(jīng)構(gòu)造初始化了的資源都會(huì)被析構(gòu)函數(shù)自動(dòng)釋放掉。然而如果使用try-finally的話(huà),f()就變成了:

              void f()
              {
                try {
                  acquire resource1;
                  … // #1
                  acquire resource2;
                  … // #2
                  acquire resourceN;
                  … // #N
                } finally {
                  if(resource1 is acquired) release resource1;
                  if(resource2 is acquired) release resource2;
                  …
                  if(resourceN is acquired) release resourceN;
                }
              }

            為什么會(huì)這么麻煩呢,本質(zhì)上就是因?yàn)楫?dāng)執(zhí)行流因異常跳到finally塊中時(shí),你并不知道執(zhí)行流是從#1處、#2處…還是#N處跳過(guò)來(lái)的,所以你不知道應(yīng)該釋放哪些資源,只能挨個(gè)檢查各個(gè)資源是否已經(jīng)被申請(qǐng)了,如果已申請(qǐng)了便將其釋放;要能夠檢查每個(gè)資源是否已經(jīng)被申請(qǐng)了,往往就要求你要在函數(shù)一開(kāi)始將各個(gè)資源的句柄全都初始化為null,這樣才可以通過(guò)if(hResN==null)來(lái)檢查第N個(gè)資源是否已經(jīng)申請(qǐng)。

            最后,RAII其實(shí)是scope(exit)的特殊形式。但在資源釋放方面,RAII有其特有的優(yōu)勢(shì):如果使用scope(exit)的話(huà),每個(gè)資源分配之后都需要用一個(gè)scope(exit)跟在后面保護(hù)起來(lái);而如果用RAII的話(huà),一個(gè)資源申請(qǐng)就對(duì)應(yīng)于一個(gè)RAII對(duì)象的構(gòu)造,釋放工作則被隱藏在對(duì)象的析構(gòu)函數(shù)中,從而使代碼主干保持了清爽。

            總結(jié)

            本文討論了錯(cuò)誤處理的原因、時(shí)機(jī)和方法。錯(cuò)誤處理是編程中極其重要的一環(huán);也是最被忽視的一環(huán),Gosling把這個(gè)歸因于大多數(shù)程序員在學(xué)校的時(shí)候都是做著大作業(yè)當(dāng)編程練習(xí)的,而大作業(yè)鮮有老師要求要妥善對(duì)待錯(cuò)誤的,大家都只要“work on the one true path”即可;然而現(xiàn)實(shí)世界的軟件可必須面對(duì)各種各樣的意外情況,往往一個(gè)程序的錯(cuò)誤處理機(jī)制在第一次面對(duì)錯(cuò)誤的時(shí)候便崩潰了… 另一方面,錯(cuò)誤處理又是一個(gè)極其困難的問(wèn)題,其本質(zhì)困難來(lái)源于兩個(gè)方面:一,哪些情況算是錯(cuò)誤。二,如何做到錯(cuò)誤安全(error-safe)。相較之下在什么地點(diǎn)解決錯(cuò)誤倒是容易一些了。本文對(duì)錯(cuò)誤處理的問(wèn)題作了詳細(xì)的分析,總結(jié)了多年來(lái)這個(gè)領(lǐng)域爭(zhēng)論的結(jié)果,提供了一個(gè)實(shí)踐導(dǎo)引。

            下期預(yù)告

            關(guān)于在C++里面為什么不應(yīng)該使用異常規(guī)格聲明(exception specification),參考文章后面的延伸閱讀[5]。

            關(guān)于如何設(shè)計(jì)異常繼承體系,checked exception/unchecked exception之爭(zhēng),關(guān)于異常處理的迷思、謬論、誤解,等等同樣有意思的問(wèn)題,請(qǐng)聽(tīng)下回分解(如果有下回的話(huà):-))。

            延伸閱讀

            書(shū)

            [1] 《C++ Coding Standards: 101 Rules, Guidelines, and Best Practices》,“Error Handling and Exceptions”部分,第68至75章。

            [2] 《The C++ Programming Language, 3rd special edition》,第14章,尤其是14.4.7、14.5、14.8。第21章的21.3.6。以及附錄E。

            [3] 《The Design and Evolution of C++》,整個(gè)16章。

            [4] 《Exceptional C++》,整本書(shū)。

            [5] 《Exceptional C++ Style》,第11、12、13章。

            [6] 《The Java Programming Language, 4th edition》,第12章。

            [7] 《Effective Java》,Item39至Item47。

            [8] 《Object Oriented Software Construction, 2nd edition》,第11、12章。

            文章

            [1] B. Stroustrup: Programming with Exceptions(pdf)

            [2] B. Stroustrup: Exception Safety: Concepts and Techniques

            [3] D. Abrahams: Error and Exception Handling

            [4] D. Abrahams: Exception-Safety in Generic Components

            [5] Tom Cargill: Exception Handling, A False Sense of Security

            [6] Damien Katz: Error codes or Exceptions? Why is Reliable Software so Hard?

            [7] Alan Griffiths: Here be Dragons

            [8] Ned Batchelder: Exceptions vs. status returns

            [9] Ned Batchelder: Exceptions in the rainforest

            [10] Ned Batchelder: Fix error handling first

            [11] A. Alexandrescu, P. Marginean: Change the Way You Write Exception-Safe Code — Forever

            [12] Brad Abrams: Exceptions and Error Codes

            [13] A. Alexandrescu: Exception Safety Analysis(pdf)

            [14] Brian Goetz: Exceptional practices

            [15] James Gosling: Failure and Exceptions

            [16] Sun: Advantages of Exceptions

            [17] Vishal Kochhar: How a C++ compiler implements exception handling

            (注:以上皆是與本文相關(guān)的,與下一個(gè)議題相關(guān)的文章就暫不列出了)

            本文來(lái)自CSDN博客,轉(zhuǎn)載請(qǐng)標(biāo)明出處:http://blog.csdn.net/pongba/archive/2007/10/08/1815742.aspx

            posted on 2010-06-12 11:01 溪流 閱讀(615) 評(píng)論(0)  編輯 收藏 引用 所屬分類(lèi): C++
            欧美亚洲国产精品久久高清| 91久久精品视频| 久久久久青草线蕉综合超碰| 偷窥少妇久久久久久久久| 伊人久久综合无码成人网| 久久久久久综合一区中文字幕| 久久久久国产精品三级网| 2021国产精品午夜久久| 久久国产一区二区| 无码人妻久久一区二区三区蜜桃| 久久精品人成免费| 亚洲国产天堂久久久久久| 乱亲女H秽乱长久久久| 久久精品国产精品亚洲| 性做久久久久久久| 久久久WWW成人免费毛片| 97r久久精品国产99国产精| 性做久久久久久免费观看| 久久久久久综合一区中文字幕| 久久久久亚洲AV成人网人人网站| 99re久久精品国产首页2020| 日产久久强奸免费的看| www亚洲欲色成人久久精品| 精品久久久久久无码专区| 国产精品久久久久a影院| 99久久婷婷国产综合精品草原| 一本久久a久久精品vr综合| 久久天天躁狠狠躁夜夜不卡| 国产精品久久波多野结衣| 亚洲欧洲日产国码无码久久99| 欧美性大战久久久久久| 久久99精品九九九久久婷婷| 99精品伊人久久久大香线蕉| 国内精品伊人久久久久av一坑| 午夜欧美精品久久久久久久| 久久大香萑太香蕉av| 久久精品桃花综合| 无码国内精品久久综合88| 亚洲av成人无码久久精品| 久久精品国产亚洲av高清漫画 | 国产免费久久精品99久久|