在平常的編程過程當中,不可避免地須要處理錯誤的狀況,而每一種編程語言都自有其錯誤處理邏輯,其背後的考量是什麼?下面來探討一下各編程語言中的錯誤處理,嘗試總結出一些通用的方法與原則。
討論一個問題以前,第一步就是要明晰下它所涉及的概念。
首先,標題所說的錯誤是廣義的錯誤,它包括異常(Exception)與錯誤(Error)。下文中提到的『錯誤』均爲狹義的區別與異常的錯誤。html
程序中的異常(Exception)是指發生在程序執行過程當中非頻繁非正常的事件,它位於程序正常流程以外。
異常大體可分爲兩類:java
編程語言中的異常則屬於軟件異常。編程
而程序中的錯誤(Error),一般是指發生在程序執行過程當中正常的事件,它就在程序正常流程範圍以內。編程語言
與異常概念最容易混淆的就是錯誤。
兩者一般能夠經過下面三個維度來區分:是否正常/可預期/終止程序ide
概念 | 是否正常 | 是否可預期 | 是否終止程序 |
---|---|---|---|
異常 | 否 | 否 | 是 |
錯誤 | 是 | 是 | 否 |
前兩個維度主要是對概念的描述,最後一個維度(是否終止程序,便是否可恢復)建議做爲定義錯誤與異常的標準。若是一個事件它不可恢復應該定義爲異常,及時終止程序退出,避免程序進入不可預知的狀態(如形成數據不一致);若是一個事件能夠預測出錯誤,那麼就應該check,並作一些相應的恢復處理。如Golang中的Error與Panic就是遵循該原則而設計。函數
區分了異常與錯誤,下一步則是考慮針對錯誤的處理機制。
正確地區分了異常與錯誤的概念,咱們就能夠根據具體場景,正確地定義出異常與錯誤,以及安排相應的錯誤處理。性能
一般,編程語言中的錯誤處理能夠分爲兩類:操作系統
雖然目前主流編程語言中的異常處理均採用『try/catch』式的原則,可是大都數在寫代碼過程當中都是左右開弓的,依據具體的場景,選擇合適的處理方式(拋異常 or 檢查返回值)。設計
最先的C語言是經過檢查函數返回值(一般零值/非空成功;非零值/空失敗)來進行錯誤處理的。
如定義一個函數:code
int foo() { // <try something here> if (failed) { return 1; } return 0; }
調用者則在進行下一步操做以前,須要判斷foo函數返回值:
int err = foo(); if (err) { // Error! Deal with it. }
基於C或者底層級別的系統均是經過這種檢查返回值的方式來處理錯誤的。如Window和Linux操做系統級別的調用(API)。
這種方式很簡單,代碼可讀性也較好,可是寫起來很是繁瑣,這意味着你須要對每個函數在調用以前的都須要手動check一下。並且,一旦忘記檢查,很容易出現bug。
Golang則在C語言的基礎上增長了更符合現代編程語言的語法和庫。它容許函數有兩個返回值,一般最後一個返回值爲Error類型,調用者能夠經過檢查該類型返回值來檢查函數返回狀況,沒有錯誤則使用第二個返回值,繼續接下來的業務邏輯操做。
如:
func foo() (int, error){ // <try something here> if (failed) { return -1, errors.New("something error") } return 0, nil; }
調用:
if sum, err := foo(); err != nil { // Deal with the error. } // do something with sum ...
Golang的實現方式看起來比C語言更加優雅一些,可是頻繁地檢查返回值仍然不可避免。
C語言在不使用goto語句的狀況下,異常代碼複用幾乎不可能,Golang也難以解決這個問題。
因而在後來發展起來的面向對象編程語言中,大部分都引入了相似try/catch式的異常處理機制。
下面主要以Java語言舉例說明
Java中全部的錯誤處理均基於Throwable頂層父類,其下有兩個子類:Error,它表示不但願被程序捕獲或者是程序沒法處理的錯誤。另外一個是Exception,它表示用戶程序可能捕捉的異常狀況或者說是程序能夠處理的異常。
這是Java對異常與錯誤的劃分,而且在此基礎上爲進一步提升程序健壯性,引入了checked異常與unchecked異常概念:針對那些除了RuntimeException與其子類,以及錯誤(Error),其餘的都是須要編譯時強制檢查的異常,不然編譯器會報錯。
try { method(); } catch (IOException ioe) { System.out.println("I/O failure"); } // ... void method() throws IOException { throw new IOException("some text"); }
也正是Java的這種處理方式讓人詬病:checked異常容易讓相關代碼裏充斥着大量的try/catch,使代碼一樣變得晦澀難懂。同時,checked異常所起到的做用也只是將捕獲的異常,包裝成運行時異常,而後再從新拋出。
正如,前文所言,每一門編程語言在設計之初都有自身的考量,且在進行實際的錯誤處理時均會同時考慮『check』與『try/catch』兩種方式。
在C#中,並無引入checked異常概念,而是把檢查的義務又『還給』了開發者。
除此以外,try/catch式異常處理一般會有很大的性能開銷,故應當慎用。
即便發展至今,關於異常處理(『try/catch』)與檢查返回值(『check』)這兩種錯誤處理方式仍然爭議不斷。
check式與try/catch式兩種錯誤處理的方式,沒有哪種是絕對優點的,都有各自的優缺點,這有賴於語言設計者當時的權衡與抉擇。可是無論哪一種編程語言,基本衍生於這兩類處理方式。
http://www.cs.tut.fi/~popl/ny...
http://joeduffyblog.com/2016/...
https://www.zhihu.com/questio...
https://nedbatchelder.com/tex...
https://www.javaworld.com/art...