什麼時候用異常,什麼時候用斷言?
如何區分可檢查的運行時錯誤,不可檢查的運行時錯誤,以及異常,感覺有些難以區分?
assert用在那些你知道絕對不會發生的事情上,但是因為人總是會犯錯誤,保不准你寫出來的東西跟你想的不一樣。所以assert用來捕捉的是程序員自己的錯誤。
同理,exception捕捉的是用戶或者環境的錯誤。
關於異常和斷言,個人以為,闡述最清楚的當屬「契約式編程」。簡而言之,檢查前條件使用ASSERT,檢查後條件使用異常。
以一個函數為例,它要求在開始執行的時候滿足一系列條件,這些條件被稱為「前條件」或者「先驗條件」,比如,參數不為空,某全局變數應該為1,等等。不滿足前條件,是不能調用此函數的,如果出現了前條件不滿足仍然調用了此函數,可以認為這是一個設計錯誤。
檢查前條件,可以使用ASSERT。這個函數執行以後,也會滿足一系列條件,這些條件被稱為「後條件」或者「後驗條件」,比如返回值滿足什麼關係,某全局變數設置稱為什麼什麼,等等。這應該是函數執行的結果,在前條件滿足的情況下,後條件如果沒有滿足是一種不正常的情況,那麼使用異常來處理。
比如函數strcpy,它的前條件是 1、 第一個參數是一個NULL結尾的字元串;2、第二個參數所指向的內存空間足夠用來複制。那麼我們可以用ASSERT來檢查是否滿足這兩個條件。它的後條件應該是 1、第二個參數所指向的內存空間中的字元串和第一個參數裡面的是一樣的。在函數執行完畢以後,我們可以檢查一下是否一樣,如果不一樣,可以拋異常(比如當時這個空間正好被另外一個線程寫入了一些東西導致異常,或者由於系統原因這塊內存同時也分給了別人等等)。
當然實際上strcpy處於效率考慮沒有做這些檢查。契約式編程中前條件不滿足是程序錯誤,需要修改,這也與一般理解中出現ASSERT是程序錯誤符合;後條件不滿足往往也是一些意外原因,也和一般理解中出現異常是意外情況符合。但是契約式編程還有一個問題是「不變式」,不變式指在函數執行之前和之後都不發生變化,我理解,在函數執行以前可以用ASSERT對不變式進行檢查,在執行以後如果發現不變式發生了變化,那麼應該拋出異常。
對於契約式編程我也沒有系統的研究過,只是以前也遇到這個問題的時候,就事論事的學習了一下,歡迎拍磚。
見: 契約式設計Design by contract怎樣解釋 Design by Contract (契約式設計)?斷言表示程序寫錯了,只要發生斷言(更正:此處應為斷言失敗),意味著至少有一個人得修改代碼。它的性質如同編譯錯誤。
例如一個函數規定某輸入參數非空,來個斷言。如果調用者送了空參數觸發斷言失敗,要麼調用方改代碼不傳空參數,要麼被調用方改代碼允許空參數處理。
如果代碼書寫完全正確,但因外界環境或者用戶操作仍然可能發生的事件,都不適合用斷言,可以使用異常,或者條件判斷處理。
至於異常,對不同語言來說含義不同。不可一概而論。題主看到你的問題分類是C++,並無Java或者其他語言,那麼我假設是C++的斷言。簡單的說,當你分不清的時候就不要分清楚它,隨意用,只要能滿足你的需求就好了。不用斷言不用異常的代碼一大堆。等你熟練了你就會有自己風格。但是以下的幾個事實最好事先記得。1. 不是所有的C++編譯器都啟用了異常功能,或者編譯器支持,認為的通過編譯選項禁掉了這個功能。2 基於宏的斷言就是檢查一個值是不是滿足你的期待,不是直接退出程序,你沒有恢復問題的餘地。3 基於宏的斷言,一般是在DEBUG編譯模式才生效的,Release出來的代碼這些斷言是沒效果的。也就是你的用戶拿到的程序里沒有這些斷言。(因為assert是程序員的調試工具啊)你是不是能夠接受?4 C++11有static_assert這個關鍵字,它不是宏。但是他的用途是在編譯期檢查一些常量是不是符合預期。
知道了這麼多,你就可以隨便用了。
個人感覺斷言是你在程序中添加的一種「契約」,表明這裡這個值應該是什麼,常見在防禦式編程和契約式編程。異常主要(不是runtime exp)是對程序無法避免的錯誤的錯誤處理。類似於error code。
在大型工程系統中(除了java),從軟體工程的設計考慮,斷言和異常都不常用。可以參見google golang的設計。上面說,在他們的實踐中發現,使用異常讓開發者很多時候生產出複雜的代碼以及關心一些沒有必要地異常。而assert確實十分方便,但是再實踐中通常會讓開發者迴避去思考一些十分重要的錯誤處理。詳細資料請參考《代碼大全2》第八章:防禦式編程。比知乎回答詳盡。
斷言用於處理不應該在程序中出現的情況,出現的時候認為程序出錯了,需要根據debug宏來在調試器開啟,發布時去掉.
異常處理程序中可能出現的錯誤情況,出現了需要做處理,程序繼續往下走,沒有調試/發布版的區分.assert處理的是 開發期 錯誤,在release代碼上,assert是會移除的,所以,assert用來檢查 程序員的錯誤。
如果你的程序模塊化做得好,用assert可以保證所有錯誤不出模塊,遇到錯誤就被攔截。
應用場景舉例:- 檢查參數有效性
- 檢查數組越界
- switch/if控制流中的can"t reach here放一個
- 不要free空指針
但是呢。。。也不是說每個數組都要檢查越界,不是每個參數都要來一條assert。。。關鍵是要平衡,在重要代碼處放上幾個就可以了。
那麼,什麼是重要代碼?怎樣是達到平衡?。。。如果你不明白這個,說明你需要更多練習。你可以從給所有自己寫錯的代碼加上assert開始。。。。。異常是語言層面的機制,每個語言都不一樣啊。。。
有的語言的異常就不是拿來用的,有的語言則鼓勵你放個屁都要試一試程序的運行期錯誤,比如網路程序網線被狗啃斷了、寫文件發現硬碟滿了、許可權不夠。。。。。。。。
錯誤發生了必須被偵測到,然後必須處理,普通錯誤可以用檢測函數返回值等方式捕捉,處理錯誤你可以寫log、給提示、崩潰等等等等。 某些奇葩的錯誤檢測、處理用異常 可能 會更好
所以,我對異常使用的建議是:盡量不使用。。。
異常及異常處理是程序流程的一個重要組成部分, 斷言不是. 斷言是一種調試工具.
In&
使用異常還是使用斷言關鍵在於你想要你的程序是穩定性重要還是正確性重要, 不同的軟體對穩定性和正確性的要求是不一樣的, 如果你想要正確性就使用斷言(讓程序立馬奔潰有助於你儘早發現錯誤, 所以斷言是在debug模式下有效), 如果你想要穩定性就使用異常。
用異常的場合是你必須明確知道這段代碼哪個地方可能會引起異常以及異常時如何產生的 否則 如果只是指望異常能讓程序更健壯 隨便一try 那麼程序往往很脆弱
不請自答。對於我來說,c++的斷言和異常是不同的。斷言表示你對程序流的某種估計,用在更多的暴漏後續調試過程中可能修改的代碼上。異常是對程序流程的另一種估計,通常是估計不足,常用來處理所謂「錯誤」的狀態。斷言不多說了。說說異常,本小菜鳥只用異常來處理程序的錯誤狀態,即除了狀態碼和錯誤碼(總之更種類型的返回值吧)以外的狀態。返回值所有的狀態包括某某失敗都認為是正常的,剩下的就讓萬能的異常來處理嘍!
按我的理解,在 Java 中:
- 斷言表示非常顯然的錯誤,比如你把構造函數聲明為private了又沒有在類里被調用,構造函數里就可以拋個AssertionError
- RuntimeException 表示程序寫錯了
- checked exception 表示即使程序寫對也有可能拋的異常
在C++中,google coding style的建議是只用assert
推薦閱讀:
※以後想做大型遊戲(至少是端游,不是手游),不知道是不是一定需要精通C++或者熟練?
※為什麼老師不推薦用《C++ Primer》作為教材?
※C++ 在哪些設計原則的指導下,變得越來越複雜?
※C++ 程序員怎麼寫簡歷?
※C++ 的 sizeof 是怎麼實現的?