異常(exception)和執行失敗有什麼區別?

例如一個User Class 的 add 方法,在成功的情況下返回用戶對象實例,在失敗的情況返回False並可以通過getError方法獲取失敗原因字元串........

說到這裡,我好像明白了,難道add方法總是應該返回用戶對象,否則拋出異常嗎?

但是這樣的話,他們的代碼量沒什麼區別的啊。問題在於即使調用add方法處沒有捕捉異常,該異常也能進一步向上拋出直至被處理或引發進程崩潰?可是說到底,這和程序自然崩潰有什麼區別呢?

---- 以上為自言自語,下面是問題 ----

拋出異常和返回false的區別是什麼,兩者在什麼場景下使用?


提醒一下,題主你用錯了一個術語:執行失敗。這個術語指的是一個非常嚴重的情況:程序已經產生了諸如段錯誤、非法指令等致命問題,無法自己執行下去,只能靠操作系統干預,強行中止。這種現象是我們常說的程序崩潰(crash)。如果操作系統本身出現了這種問題,就表現為系統崩潰和死機等現象。而無論是返回錯誤碼,還是使用異常,廣義上說,都是程序自身正常流程的一部分,並沒有上述致命問題。即使因為未處理異常而導致程序退出,同樣也是依靠程序自身的流程,不需要操作系統額外干預。嚴格說來,這種情況其實不能算是真正崩潰(crash),只是程序自行退出。

當然,題主你的意思是要問使用異常和返回錯誤碼有什麼區別。從功能上來說,他們都可以做到底層發現問題-&>報告錯誤消息-&>上層接收消息-&>處理問題這幾件事。絕大多數場合下,兩種辦法都可以,靠你自己去選擇。兩種都用也是可以的。

通常來說,處理不正常的情況,建議使用異常。因為異常是專為報告錯誤而設計的語言特性,返回值則不是。異常會有一些獨有的優勢。

  • 向上傳播異常不需要你寫額外的代碼。
  • 異常使用的是一個獨立的傳播途徑,不需要佔用你的返回值,因此不需要專門設計特定的返回值來指示錯誤。
  • 異常可以是一個相當複雜的對象。如果你需要攜帶很多信息,異常比返回值方便。
  • 異常可以是類對象,而類對象擁有多態性,這方便你控制錯誤處理的粒度。你可以精確處理FileNotExist這樣的錯誤,也可以簡單地將IOError統一處理。
  • 某些場合下(如C++構造函數)是沒有返回值的,要第一時間報告錯誤,只能使用異常。
  • 異常不能忽略,你必須處理,否則程序將會退出。返回值你不處理也不會發生什麼。

不過異常也有一些劣勢。

  • 異常拋出點也是函數的一個出口,初學者常常忘記這一點,導致沒有回收資源。你需要在寫一個函數的時候,將異常也考慮進去。

  • 異常在底層的實現方式,並沒有統一標準。如果你混用的兩種二進位代碼不幸使用了不同的實現方式,異常跨界傳播過程中很可能造成程序段錯誤崩潰。嚴格來說,其實返回值也有這個問題(函數調用約定),但是編譯器和源代碼中通常都對此做了足夠的包裝處理,而異常沒有這個待遇。

熟悉了優劣之後,大部分情況下,建議使用異常來報告處理不正常的情況,而使用返回值來報告正常執行的結果。實際真正需要權衡和思考的問題往往不在於使用哪種設施,而在於將什麼樣的情況視為「不正常」。

  • 處理用戶輸入的時候,發現格式不對,是否視為「不正常」?
  • 讀取程序配置文件的時候,發現沒有這個文件,是否視為「不正常」?

看起來這都是「不正常」的現象,但也許特定場合下都可以直接處理掉而不報告。比如無效輸入也許可以直接用一個默認有效值,或者空白值來代替。比如配置文件不存在的場合下,可以直接使用默認配置。這些都是需要在實際場合中才考慮的問題。


都是錯誤處理的策略,OO語言更傾向異常,相比較判斷一個函數可能的錯誤狀況來說,「拋出」狀態完整的異常對象被認為是更好的封裝 ;你說的情況下,錯誤即false是最簡單的情況,這種情況下,異常未必有優勢;一般認為用異常可以簡化複雜的錯誤處理代碼,並且只要程序不需要終止執行,try catch比if else的代碼更可讀(錯誤處理的策略明顯,狀態可以更豐富,雖然這不是絕對的)。

result = func();
if (result === ERR_NO_1) {
...
} else if (result === ERR_NO_2) {

} else {
...
}

try {
result = func();
} catch (CustomException1 e) {
..
}
catch (CustomException2 e) {
..
}
finally {
..
}

有些異常不會立即處理,而是往上拋,runtime需要維護一個棧,所以異常會有額外開銷,但通常不重要;好處是程序員不需要特別維護多層調用的錯誤鏈,A =&> B =&> C,如果C調用拋出異常,程序員可以在B或者A里catch,不需要改動B和A的介面(返回同樣的錯誤鏈);如果C返回錯誤,假如要在A或B里處理,就必須傳遞C的錯誤鏈;所以用異常的好處是可以簡化介面設計。

但最終,異常的好處來源於它更好的封裝和runtime的支持。


首先,這是一個好問題。深入理解二者的區別是突破中等程度程序員的標誌之一。

先澄清一個誤區,那就是異常非常慢。異常的實現需要保存異常拋出點到異常捕獲點的必要信息,這是人們詬病異常性能的主要依據。但是這是錯誤的。第一,手工按照返回錯誤的風格取得行為等價於異常的實現性能絕大多數時候比不上語言內置的異常的性能。對於支持原生異常的OS,如Windows,這個差距尤其明顯;第二,對於非基於棧的調用實現,異常和返回錯誤性能幾無差別,只是風格不同而已。

回到問題,異常和返回錯誤的本質區別是什麼?答:異常是強類型的,類型安全的分支處理技術;而返回錯誤是其弱化的,不安全的版本。

先看返回錯誤的特徵。一般來說,需要用特別返回值來標識錯誤,意味著需要重載特定值得含義。假設一個操作會返回一個int型值,因為其不可能返回負數(如求絕對值),我們一般可以使用-1來作為操作失敗(如函數庫調用載入錯誤)的指示。-1在int類型中並無特別含義,然而我們這裡強制施加了語義,而這種語義是弱的。沒辦法通過類型系統檢查來確保正確性。

一個增強的辦法是同時返回兩種值。有的語言內置支持,沒有內置支持的可以通過定義一個含有分別表徵正確返回類型和錯誤類型的結構來模擬。這樣的好處是傳遞信息的通道被分離了。

無論如何返回錯誤,它存在著一個顯著的缺點:調用方必須顯式檢查錯誤值。當錯誤發生,必須終止當前操作,返回錯誤的時候,一般需要定義全新的錯誤類型。這導致操作不可任意簡單組合,如模塊B調用模塊A發生錯誤的時候需要返回B定義的錯誤,而模塊C調用A錯誤時則需要返回C定義的錯誤,而這兩類錯誤往往是不兼容的。組合相關代碼必須加入大量的粘連代碼,這導致了代碼膨脹,而且非常容易出錯。對於介面定義明確的系統,一般定義一套通用的錯誤碼來簡化其複雜性。OS內核就是一個例子,它提供了一個適配所有OS調用的錯誤碼錶。然而其內部使用的錯誤碼要多得多,並且往往在介面把內部錯誤碼映射到外部錯誤碼。

異常是一種安全的分支處理技術。因為它和錯誤處理密切相關,人們容易直接把異常看成是錯誤處理,連名字「異常」都帶著濃濃的錯誤的含義。但是這是一個誤解。異常的理論基礎是假設某些分支處理中,一個分支和其它分支比起來發生的非常不頻繁。與其平等地針對常見和極其罕見的情形進行處理(想一下,正常處理代碼和錯誤處理代碼往往一樣多,大部分情況下後者其實更多),不如僅僅處理正常的情形,把不常見的情形歸於一處統一處理。這樣我們書寫代碼的時候僅僅關注正常情形就可以了。發生錯誤的時候,特別的流程會幫助程序員直接回到定義了錯誤處理的地方。

這樣說有點過分簡化。一般情況下,不能直接跳回。因為在異常拋出點到異常處理點之間,可能存在處於非一致狀態的值,等待釋放的資源等。所以一般情況下,特殊流程要回溯調用棧,確保執行的事務性。但是僅僅這個特殊流程是不夠的,必須有合適的配套代碼才行。這導致了額外的複雜性。C++引入的RAII概念是一個創舉,然而依然沒能全部消除程序員的工作。

因為異常是一個具體的類型,一個函數的signature就不僅僅是輸入參數列表,輸出參數加函數名,還要包含可能拋出異常列表。因為各個模塊定義的異常層次結構迥異,這導致了額外的組合困難。然而和返回錯誤值的組合困難不同,前者導致的是編譯時類型錯誤,後者則是運行時錯誤。一般來看,錯誤暴露的越早越好,我們傾向於前者。

理想情況下,一個異常結構完備的系統不會有運行時錯誤(大概如此,不展開)。然而因為上面提到的原因,在現有的異常支持的情況下,代碼同樣複雜,冗餘,難以維護。

上面就是異常和錯誤的基本區別。

第二部分預告:有解決上述問題的優雅方法嗎?

我的觀點是有。但是因為現有能力還不到不藉助任何參考直接澄清此問題,我回留待仔細斟酌,沐浴焚香,換到非手機輸入的情況下給出,盡請期待。


用異常就不用每次調用方法之後都人肉拆包加判斷(弄多一層括弧——你看,層次結構出來了吧?monad 有了吧?)了啊

嗯這本質上就是個 monad 罷了

unit x = Result(x)
map Result(x) f = Result(f(x))
map Except(e) f = Except(e)
join Result(Result(x)) = Result(x)
join Result(Except(e)) = Except(e)
join Except(E) = Except(E)


你少說了一種情況,其實函數執行失敗有三種情況:

1,返回錯誤。

2,程序崩潰。

3,拋出異常。

其實這三種情況的邊界,是可以自己斟酌的,最理想的情況是,三種的並集為全集,交集為空集。然而大部分人其實做不到這麼清晰(反正我做不到)。

大致上,我的原則是:

1,如果函數被傳入了錯誤參數(參數範圍在文檔中寫明),則程序立即崩潰(可以使用 release 下也生效的 assert 保證這一點)。此時外部調用代碼是必然錯誤的,需要修改。

2,如果傳入參數與文檔一致,但是由於內存中其他狀態導致功能無法執行(比如socket還沒連接就發送數據)。如果這個函數被設計為返回錯誤碼,則返回錯誤碼,否則應立即崩潰。這裡不適合拋出異常,因為嚴格的說所有的異常都應該被 catch,而如果外部的代碼正確,這裡是不可能有異常的。

3,只在一種情況下拋出異常,即代碼完全正確不需要修正(修正不等於優化),但是由於運行環境的原因無法執行功能(比如要打開一個文件結果文件不存在)。

原則說起來很清楚,但是真正寫起來,還是經常混淆的。


一個很簡單的例子,你要解析json的請求回包,只返回false的代碼可能是

if(jsonObj.has("xxx"))
{
if(jsonObj.has("xxxxx"))
{
//.........
return true;
}
}

return false;

但是如果用異常

try
{
int a = jsonObj.get("xxxx");
int b = jsonObj.get("yyyyy");
return true;
}
catch(JsonException e)
{
return false;
}

這代碼簡潔度和可讀性不能比啊。


異常能夠保留更多的信息,比如異常類型、描述、堆棧,能附帶立即中止當前過程的效果,最重要的是,它保證一旦沒有正確處理這個異常就中止所有後續過程。返回錯誤碼的設計缺陷在於如果不處理則默認忽略,如果調用方不恰當地忽略了異常,可能會造成更多嚴重錯誤,而對異常來說,大多數情況下不處理內部異常而是直接傳遞給上一級調用方是可以接受的。對現代業務系統來說,許多過程調用是通過webservice等RPC方式進行的,異常可以由框架統一捕獲,通過調用協議傳遞給調用者,最終通知到系統管理員或用戶,很方便問題排查與解決。


拋出異常的函數,就不需要返回bool。

設計成返回bool或指針的函數,就不需要異常。

也就是說異常和返回值是永遠不同時存在的(當然這是我個人的理解)。

(我覺得,你問題中的add函數如果有可能失敗,那麼就不拋異常,如果正常情況下不應該失敗,那麼可以拋異常。但並不一定需要在每一個add函數的地方都加try。加不加得看你的程序邏輯能否以及應該恢復..(這時你也可以寫一個輔助函數在其內部調用try add..其他地方使用這個輔助函數:),繼續跑。否則就在程序入口加個try,然後退出即可)

好像還沒答全你的這個好問題,明天電腦上再討論討論。


第一,異常都是可以預見的,所以它有可預知性。第二,異常不關心發生的條件而關心如何善後。第三,異常是被定義過的錯誤,比較容易定位問題。第四,異常可以傳遞。

執行失敗的結果就是直接退出。。。。


假設你對某個異常(或者說執行錯誤)需要這樣處理:

——在用戶界面顯示一條錯誤信息。

但是這個異常是由底層的API,比如資料庫調用拋出的,而客戶端沒有直接調用這個API的許可權。

這時候,你可以選擇:

  1. 拋出異常,一層一層拋出
  2. 定義錯誤代碼,一層一層返回

你選2,

  1. 發現每一個函數都增加了一個叫err的傳入參數,這個參數並沒有什麼卵用,因為它會被不加處理地返回給上一層,直達UI。

  2. 這並沒有什麼問題,但是有一天你被告知某個底層改動導致這個API會拋出一種新的錯誤,老闆要求你對這種錯誤在調用棧里的某一層處理掉。於是你增加了對異常類型的判斷。

  3. 後來,另一個新的模塊需要復用你在上次處理的那個module里寫的異常信息,你為了不cp代碼,把異常信息封裝成了一個Error類,每個從Error繼承來的子類都保存著自己獨有的異常信息。

  4. 你對自己造的輪子很滿意,然後用到了現在維護的模塊中——現在你的代碼看起來是這樣:

    def f(err, **kwargs):
    result = None
    if(err.code == 1):
    handle_this_err(err)
    elif(err.code == 2):
    return (err, result)
    else:
    result = handle_data(**kwargs)
    return (None, result)

  5. 後來你覺得每次都把err這個參數傳來傳去很麻煩,如果有什麼方式不用顯式地傳遞這個參數就好了。或許可以維護一個棧,用來專門存放調用信息和錯誤信息之間的關係?
  6. 你想了想,這個東西除了不叫異常,這tm不就是異常么!

=================================

其實就算沒有異常這個機制,當複雜度上去了,你也會自己去封裝異常信息的

異常機制只是語言提供給你的方便。

你可以不用它的異常機制自己搞一套,但是作為一個程序員,不能少了「不造輪子的覺悟」。


誰規定的錯誤碼必須是一個整數?異常能定義成對象就變成異常特有的好處了?這樣的邏輯推導出來的工程實踐真是可笑。


根據我多年划水經驗

你有能力處理的問題叫錯誤,

沒有能力處理的問題叫異常。

比如資料庫崩潰,這種問題作為開發者是沒能力解決的,所以歸類於異常,對付異常的最好辦法就是讓程序崩潰。又比如你想取一條資料庫中不存在的數據,這就是錯誤,你可以打 log, 也可以重試,因為你有能力處理這個問題。


能返回Either或者Maybe就方便了, 像Either這樣錯誤信息夠豐富的情況下根本不需要異常


我比較喜歡Rust那種風格,主要是不會在你肉眼可見的邏輯流之外另搞一個邏輯。


那些鼓吹用異常代替錯誤碼的,別誤人子弟了好嗎?

function test(a)

{

try{

if(a.id)return 2;

if(a.err)throw("err");

}

catch(e){

return 1;

}

return 0;

}

console.log(test({id:1}));

console.log(test({err:1}));

console.log(test(1));

console.log(test());

test用了3個返回嗎,這是我為了更加直觀的顯示結果。實際代碼中一般就返回true,false

請問test的參數為空,或者為數字,真的是你程序的正常的情況嗎。

function a(){

.........

throw("err1");//用異常代替錯誤碼

}

function main()

{

try{

a();

b();

d();//原本應該是c();

}

catch(e){

if(e=="err1"){

}

}

}

上述代碼手誤,把c()寫成了d(),那麼這個異常也會在捕捉。

萬一在catch代碼裡面,只處理了e=="err1"等情況,那麼,這個編譯的異常被屏蔽了。本來應該馬上就能發現的錯誤,被完美的隱藏了。

當然,也可以在else那裡繼續拋出異常,把這種編譯異常跟業務錯誤碼放在一起處理,確定是高大上的先進方式?

再來個例子

test(a(b(c(d(e())))));

為了「方便」處理錯誤碼,在e裡面拋出異常代替錯誤碼。那麼異常該在a,b,c,d裡面哪個函數處理呢?

如果在d裡面處理了,d也有可能拋出異常,那麼只能在a處理。

如果只在a處理,b,c,d,不處理,那麼有一天,可能不是a來調用了,直接改成b-&>c-&>d-&>e,如果b沒有異常處理,那麼程序直接就奔潰了。萬一e裡面拋出異常的情況很少見,這又是一個很難調的bug。程序不定時退出。

那麼就a,b,c,d全部處理異常,這不是脫褲子放屁,錯誤碼不就這樣處理的嗎?

*2015/10/10更新異常實現原理及性能對比。

添加用戶,如果用戶名已經存在 ,或者密碼太簡單,那麼返回失敗。

如果是資料庫出現問題,沒法查詢,沒法插入,那麼就是異常。

失敗是正常的邏輯,異常是不可控的錯誤,像基礎服務資料庫,redis等異常導致添加用戶這個操作沒法完成,函數或者模塊是沒法處理的,則拋出異常。

###############

看到前面幾個回答,忍不住再啰嗦幾句。

異常是異常,失敗是失敗,把失敗當做異常處理,把異常當做失敗處理都是錯誤的。這是最基本的程序員思想。這個跟處理異常的cpu耗時無關,無論媽多醜,媽就是媽,無論媽多漂亮,都不能當做老婆。

還有人說開發的時間就要確定異常列表。能夠確定的只有錯誤碼,非要把錯誤碼當做異常處理我也無話可說。js,lua來說,代碼編譯錯誤就是異常,這個異常你能預見嗎?你的處理代碼裡面,要寫文件或者發送聯網消息,要是硬碟壞了,網線鬆了,你的代碼要枚舉所有異常嗎?異常不一定是你的代碼產生的。

也許有人會說編譯異常可以在開發的時候消除。但現在很多是動態迭代的,補充一個新功能或者修改一下,以前是編譯重新發布,現在很多是一直運行一直beta一直發布。react native就是可以通過js動態發布。動態載入的時候捕捉異常就很有必要,萬一載入了一個有編譯問題的代碼,整個app就癱瘓了,讓用戶重新下載?lua,js是執行時候編譯的,就是說萬一一段代碼很難執行到,編譯錯誤也很難發現。我使用lua來說經常遇到這種情況。

返回失敗是函數(模塊,組件...)邏輯的一部分,最簡單參數錯誤來說,讓客戶重新輸入重新提交就好了,你需要捕捉這個異常?

順便說說斷言。拿參數錯誤來說,如果是內部函數,比如一個typeid參數,約定好只有1,2,3三種情況,switch case1,2 ,3之後,default情況怎麼處理,一種方式是拋出異常,告訴開發人員,typeid遇到了不理解的數字,可能是多了一個4,這裡沒有修改。還有一種情況是用斷言,assert(typeid&>0typeid

另外說說異常,失敗轉換的情況。對操作系統來說,磁碟異常他不會拋出異常,他可能返回一個類似超時的錯誤。當然,業務代碼也可以封裝這個錯誤,返回一個超時的錯誤碼。這時一切都是歌舞昇平,直至n天(小時,年)以後,客戶大量投訴,甚至無法挽救。如果拋出異常,異常處理可以log給運維人員看,還能發簡訊,發郵件及時告訴運維人員。

再說說網路異常,在服務端網路異常可能是致命的,但在app來說不是。app運行過程中,可能隨時被用戶關閉網路。沒有網路對於app來說是個常見的邏輯,這時候原本應該是異常的,可以轉成錯誤碼。當然還可以用異常捕捉,捕捉的沒有網路,改成緩存,這種異常處理方式,其實就是錯誤碼處理一樣了,只不過用異常來寫。switch ret.code跟switch e.code的不同而已。

假設a調用b,b調用c,c調用d,d調用e...

他們都會遇到一個共同的錯誤,比如沒有網路,一種方式是每個函數都去判斷底下函數返回這個錯誤碼,然後返回上層。但是有可能要處理一下,比如提醒客戶沒有網路,讓客戶聯網,但是是在a,還是b,還是c來處理這個問題呢,一般來說讓功能業務頂層函數處理,但功能可能嵌套的,比如b是模塊b的頂層函數,他可能被a模塊調用,這樣處理這個聯網問題的邏輯就會有點混亂。這個時候拋出異常,從邏輯上來說更簡單點。下面函數只管拋異常,由上面模塊處理。

對於這種可以預見可以捕捉處理,不需要異常log,異常跟錯誤碼沒有明顯的界限。

c語言沒有異常處理,全部是錯誤碼,從實現上來說不用異常當然也是可以的,但是代碼會很醜,邏輯也混亂。c語言寫的程序,至少60%都是在處理錯誤碼,有時候根本跟函數的業務無關,有些錯誤碼也必須處理。c++就引入了異常。但是異常跟錯誤碼沒有高低貴賤之分,一個簡單的判斷是用異常還是錯誤碼,看看這個是否跟函數的業務邏輯有關。比如添加用戶,參數錯誤:密碼太短,用戶名為空 ,邏輯錯誤:用戶已經存在,密碼太簡單,用錯誤碼處理。調用底層函數出現的錯誤,如操作資料庫,讀寫文件,更新memcache,大部分應該用異常。

而內部約定應該用斷言(js,lua等非靜態編譯語言用異常代替)。

###############分隔符##

再來說說異常原理。

先看看cpu異常,1/0,常見的除0錯誤,這個時候讓cpu如何繼續?顯然他不知道,他只能拋出異常。還有斷電異常,他也不知道該如何處理,拋出異常。還有一種是斷點異常,他不懂怎麼處理,交給調試程序處理。

cpu異常就是一個中斷,操作系統要登記,每個中斷都有一個處理地址,叫中斷表。

還有一種指令可以觸發異常或者中斷,操作系統一般靠這個實現用戶異常捕捉。高級語言throw一個異常的時候,他是通過一個指令觸發cpu異常中斷,然後調用操作系統異常處理,操作系統發個消息給用戶進程。用戶進程再看有沒有異常捕捉程序,如果沒有就關閉程序。說異常跟返回錯誤嗎性能差不多,呵呵。從cpu指令級別來說,至少相差1000倍。還有很重要的一個緩存因素,內存對cpu來說太慢了,賽揚為啥一樣的頻率性能差,就是cpu緩存少。異常顯然打破了緩存預期,異常處理代碼在操作系統空間,用戶進程空間跟操作系統空間的切換,緩存全都沒用了。c語言沒有異常處理讓人詬病,但c語言同樣以性能稱著。

當然,再怎麼樣,現在cpu太快了,每秒執行上億個指令,即便異常相對錯誤碼耗時上千倍,也是納秒跟毫秒的區別,相對秒來說,還是很快。就像memcache一樣,把數據放在其他電腦上,給網路用戶來說,還是很快,你看,不到1秒伺服器就返回了。

c語言把所有異常用錯誤碼來返回,現在又有人想用異常來代替錯誤碼處理。只處理正常情況,是每個程序員的夢想,代碼一下子可以縮小80%。但是我想告訴大家,處理錯誤碼,那一堆switch,ifeslse,是程序員的工作,想靠異常處理來解放錯誤碼處理,想法很好,想想就好了。


用Optional大法,配合flatMap(Monad bind)還有什麼不能搞定的。你說語言原生不支持?快使用Rx系列

順便安利Swift/Rust大法


寫了一些,但是覺得沒什麼好說的。很多疑惑的問題,見得多了寫得多了自然就知道。很多時候沒有孰優孰劣,只有風格上的統一。例如C++這樣的支持exception的語言,在很多代碼庫里是完全exception free,而用類C的形式來處理異常。

異常有兩類,一類是『我知道這個地方可能出錯,調用方必須明確知道出錯的風險』,另一類是『這就是個錯誤,沒啥挽救餘地了崩潰拉倒』。這兩類異常放Java里就是checked exception和unchecked exception的區別。

異常並不僅限於錯誤處理,很多時候是用來分開處理normal scenario和edge case的。比如說讀取一個文件,把文件中每個詞出現次數統計一下。這裡用Python語法:

word_count = dict()
for word in words:
try:
word_count[word] += 1
except KeyError:
word_count[word] = 1

從這個例子可以看到,拋出異常並不代表出現了錯誤。當然這個地方代碼可以修改成

word_count = dict()
for word in words:
if word in word_count:
word_count[word] += 1
else:
word_count[word] = 1

區別在於每次循環都額外做了一次in的判斷。


從WINDOWS實現上來講一個分支語句,一個是SEH或者全局回調處理,後者可以很好的防止不再預料的情況時程序奔潰!


從實用的角度來說,異常最大的好處是當調用棧很深的時候,發生錯誤需要中止所有的後續操作,拋異常會比傳遞error code的方式簡潔的多,也僅此而已

另外checked異常強制要求捕獲看似有用,但是在調用棧深的情況下都多數會包裝成Runtime異常,不然代碼寫的蛋疼死

其他的異常能幹的事情,error code都能幹


鞋妖.

「異常」是一個比較專業的名詞,而「執行失敗」更像是一種籠統表述,所以覺得不能從定義上闡述差異。下面說些個人觀點。

「異常」,exception,本意傾向於「意料之外」。比如調用一個加法函數,你「期待」得到一個確切的結果,但返回了null,這不符合預期。還比如,你調一個API,「期待」得到

json,但返回了一個404頁面的html源碼,這也不符合預期。 但這些操作都「執行成功」了,它們是「異常」。

至於「執行失敗」,則可以理解為error。比如服務啟動失敗,比如代碼運行時內存溢出crush了,等等。

以上。


推薦閱讀:

Python是不是弱類型?如果是的話是不是僅僅因此就不需要泛型了?
windows下anaconda 安裝報錯, errno9,怎麼解決?
spyder 如何添加和安裝其他的包?
Python 做高頻交易系統適合哪個級別的延遲?
為什麼 Python 中列表的 sort 方法一定要返回 None 而不是排序後的列表?

TAG:Python | Java | Nodejs | C | 異常處理 |