編程語言中的錯誤處理

在平常的編程過程當中,不可避免地須要處理錯誤的狀況,而每一種編程語言都自有其錯誤處理邏輯,其背後的考量是什麼?下面來探討一下各編程語言中的錯誤處理,嘗試總結出一些通用的方法與原則。

1、什麼是異常

討論一個問題以前,第一步就是要明晰下它所涉及的概念。

首先,標題所說的錯誤是廣義的錯誤,它包括異常(Exception)與錯誤(Error)。下文中提到的『錯誤』均爲狹義的區別與異常的錯誤。html

程序中的異常(Exception)是指發生在程序執行過程當中非頻繁非正常的事件,它位於程序正常流程以外。
異常大體可分爲兩類:java

  • 硬件異常:由CPU發起,它們多是某些指令序列的執行致使的。好比除零或訪問非法內存地址等。
  • 軟件異常:由應用程序或操做系統顯式發起。例如系統能夠檢測到指定的參數非法值。

編程語言中的異常則屬於軟件異常。編程

而程序中的錯誤(Error),一般是指發生在程序執行過程當中正常的事件,它就在程序正常流程範圍以內。編程語言

2、正確區分異常與錯誤

與異常概念最容易混淆的就是錯誤。
兩者一般能夠經過下面三個維度來區分:是否正常/可預期/終止程序ide

概念 是否正常 是否可預期 是否終止程序
異常
錯誤

前兩個維度主要是對概念的描述,最後一個維度(是否終止程序,便是否可恢復)建議做爲定義錯誤與異常的標準。若是一個事件它不可恢復應該定義爲異常,及時終止程序退出,避免程序進入不可預知的狀態(如形成數據不一致);若是一個事件能夠預測出錯誤,那麼就應該check,並作一些相應的恢復處理。如Golang中的Error與Panic就是遵循該原則而設計。函數

3、錯誤處理

區分了異常與錯誤,下一步則是考慮針對錯誤的處理機制。

正確地區分了異常與錯誤的概念,咱們就能夠根據具體場景,正確地定義出異常與錯誤,以及安排相應的錯誤處理。性能

一般,編程語言中的錯誤處理能夠分爲兩類:操作系統

  • check式,檢查返回值,以C語言爲表明,Go亦如此;
  • try/catch式,目前大部分主流編程語言中的異常處理均採用相似方式,如C++/Java/PHP/JavaScript等。

雖然目前主流編程語言中的異常處理均採用『try/catch』式的原則,可是大都數在寫代碼過程當中都是左右開弓的,依據具體的場景,選擇合適的處理方式(拋異常 or 檢查返回值)。設計

3.1 check式

最先的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式的異常處理機制。

3.2 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式異常處理一般會有很大的性能開銷,故應當慎用。

4、『check』 OR『try/catch』

即便發展至今,關於異常處理(『try/catch』)與檢查返回值(『check』)這兩種錯誤處理方式仍然爭議不斷。

check式與try/catch式兩種錯誤處理的方式,沒有哪種是絕對優點的,都有各自的優缺點,這有賴於語言設計者當時的權衡與抉擇。可是無論哪一種編程語言,基本衍生於這兩類處理方式。

REFERENCES

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...

相關文章
相關標籤/搜索