跳轉到內容

計算機程式設計/錯誤處理

來自華夏公益教科書,開放的書籍,為開放的世界
這篇計算機程式設計文章有 虛擬碼Ada 版本。

錯誤處理技術

[編輯原始碼]

本章介紹各種錯誤處理技術。首先描述技術,然後用示例函式及其呼叫來展示其用法。我們使用 √ 函式,當用負引數呼叫時,它應該報告錯誤條件。

返回值

[編輯原始碼]
function √ (X : in Float) : Float
begin
   if (X < 0) :
      return -1
   else
      calculate root from x
   fi
end
C := √ (A2 + B2)

if C < 0  then
   error handling
else
   normal processing
fi

我們的示例利用了 √ 的所有有效返回值都是正數的事實,因此 -1 可用作錯誤指示器。但是,當所有可能的返回值都是有效的,並且沒有返回值可用作錯誤指示器時,此技術將無法工作。

錯誤(成功)指示器引數

[編輯原始碼]

錯誤條件透過額外的 out 引數返回。傳統上,指示器要麼是布林值,其中“true = 成功”,要麼是列舉,第一個元素為“Ok”,其他元素指示各種錯誤條件。


function √ (
   X       : in Float; 
   Success : out Boolean ) : Float
begin
   if (X < 0) :
      Success := False
   else
      calculate root from x
      Success := True
   fi
end
C := √ (A2 + B2, Success)

if not Success then
   error handling
else
   normal processing
fi

此技術在數學計算中看起來不太好。

全域性變數

[編輯原始碼]

錯誤條件儲存在全域性變數中。然後直接或間接透過函式讀取此變數。


function √ (X : in Float) : Float
begin
  if (X < 0) :
     Float_Error := true
  else
     calculate root from x
  fi
end
Float_Error := false
C := √ (A2 + B2)

if Float_Error then
   error handling
else
   normal processing
fi

從原始碼中可以看出,此技術的麻煩之處在於選擇重置標誌的位置。你可以讓被呼叫者或呼叫者執行此操作。

此外,此技術不適合多執行緒。

程式語言支援某種形式的錯誤處理。這從早期 Basic 方言中的經典 ON ERROR GOTO ... 到現代面嚮物件語言中的 try ... catch 異常處理不等。

其理念始終如一:你將程式的某個部分註冊為錯誤處理程式,以便在發生錯誤時呼叫。現代設計允許你定義多個處理程式來分別處理不同型別的錯誤。

一旦發生錯誤,執行就會跳轉到錯誤處理程式並在那裡繼續執行。


function √ (X : in Float) : Float
begin
   if (X < 0) :
      raise Float_Error
   else
      calculate root from x
   fi
end
try:
   C := √ (A2 + B2)
   normal processing
when Float_Error:
   error handling
yrt

異常處理的最大優勢在於它可以在一個異常處理程式中阻止多個操作。這減輕了錯誤處理的負擔,因為不必獨立檢查每個函式或過程呼叫以確認其是否成功執行。

契約式設計

[編輯原始碼]

契約式設計 (DbC) 中,必須使用正確的引數呼叫函式。這是呼叫者對契約的貢獻。如果實際引數的型別與形式引數的型別匹配,並且實際引數的值使函式的先決條件為真,則子程式有機會滿足其後置條件。否則,就會發生錯誤條件。現在你可能想知道這將如何運作。讓我們先看一個示例


function √ (X : in Float) : Float
    pre-condition (X >= 0)
    post-condition (return >= 0)
begin
   calculate root from x
end
C := √ (A2 + B2)

如你所見,函式要求 X >= 0 的先決條件 - 也就是說,只有當 X ≥ 0 時才能呼叫該函式。作為回報,該函式承諾其返回值也 ≥ 0 的後置條件。

在完整的 DbC 方法中,後置條件將宣告一個關係,該關係完全描述了執行函式時產生的值,類似於 result ≥ 0 and X = result * result。此後置條件是 √ 對契約的貢獻。使用斷言、註釋或語言的型別系統來表達先決條件 X >= 0 體現了契約式設計的兩個重要方面

  1. 編譯器或分析工具可以幫助檢查契約。(例如,當 X ≥ 0 來自 X 的型別,並且呼叫 √ 時使用的引數型別相同,因此也 ≥ 0 時,就會出現這種情況。)
  2. 可以在呼叫函式 **之前** 機械地檢查先決條件。

第一個方面增加了安全性:沒有程式設計師是完美的。每個需要由程式設計師自己檢查的契約部分都有很高的出錯機率。

第二個方面對於最佳化非常重要——如果可以在編譯時檢查契約,則不需要執行時檢查。你可能沒有注意到,但如果你仔細想想: 從來不會是負數,前提是 冪運算子和加法運算子以通常的方式工作。

我們為一段從不失敗的程式碼建立了 5 個不錯的錯誤處理示例。這就是控制 DbC 某些執行時方面的絕佳機會:你現在可以安全地關閉檢查,程式碼最佳化器可以省略實際的範圍檢查。

DbC 語言在面對契約違規時的行為方式上有所區別

  1. 真正的 DbC 程式語言將 DbC 與異常處理結合起來——在執行時檢測到契約違規時引發異常,並提供重新啟動失敗例程或以已知良好狀態阻塞的方法。
  2. 靜態分析工具在分析時檢查所有契約,並要求以一種永遠不會在執行時違反契約的方式編寫程式碼。


語言概述

[編輯 | 編輯原始碼]

此列表概述了各種程式語言中使用的標準或主要行為和錯誤處理技術。這並不意味著在所列的程式語言中不可能使用其他技術或行為。

程式語言中的錯誤處理
語言 技術 / 0 陣列 整數 浮點數 型別
Ada 月度精選 2005 年 9 月 異常/DbC1 處理 處理 處理 處理 處理 處理
C 返回/變數 崩潰 崩潰 故障 故障2 故障 故障2
C++ 異常 崩潰 崩潰 故障 故障2 故障 故障2
Python 異常 處理 處理 處理 處理 處理 處理
SPARK DbC 處理 處理 處理 處理 處理 處理
錯誤型別
空指標或空元素訪問。
/ 0 除以 0。
陣列 陣列越界訪問。
整數 整數範圍越界/溢位。
浮點數 浮點數計算越界。
型別 型別轉換越界。
處理方式
處理 以適當的方式處理錯誤——例如使用錯誤或異常處理程式。
故障 程式繼續以未定義或有故障的方式執行。
崩潰 程式崩潰。
技術
DbC 語言使用契約式設計來避免錯誤條件。
異常 語言在出現錯誤條件時引發異常或使用類似的技術。
返回 語言使用返回程式碼來指示錯誤條件。
變數 語言使用一個或多個全域性變數來指示錯誤條件。

1 : Ada 透過其強大的型別系統支援非常有限形式的 DbC。
2 : 在 C 和 C++ 中,行為已定義,有時會故意使用,但在本比較中,我們認為是無意使用。

華夏公益教科書