Ada 程式設計/錯誤處理
本章介紹各種錯誤處理技術。首先描述技術,然後用示例函式及其呼叫來展示其使用。我們使用√函式,該函式在以負引數呼叫時應報告錯誤條件。
具體地說:異常是 Ada 的正確做法。
procedureError_Handling_1isfunctionSquare_Root (X :inFloat)returnFloatisuseAda.Numerics.Elementary_Functions;beginifX < 0.0thenreturn-1.0;elsereturnSqrt (X);endif;endSquare_Root;beginC := Square_Root (A ** 2 + B ** 2);ifC < 0.0thenT_IO.Put ("C cannot be calculated!");elseT_IO.Put ("C is "); F_IO.Put (Item => C, Fore => F_IO.Default_Fore, Aft => F_IO.Default_Aft, Exp => F_IO.Default_Exp);endif;endError_Handling_1;
我們的示例利用了√所有有效返回值都為正的事實,因此-1可以用作錯誤指示器。但是,當所有可能的返回值都是有效的,並且沒有返回值可用作錯誤指示器時,此技術將不起作用;因此,使用此方法是一個非常糟糕的想法。
錯誤條件透過額外的out引數返回。傳統上,指示器要麼是布林值,其中“true = 成功”,要麼是列舉值,其中第一個元素為“Ok”,其他元素指示各種錯誤條件。
procedureError_Handling_2isprocedureSquare_Root (Y :outFloat; X :inFloat; Success :outBoolean)isuseAda.Numerics.Elementary_Functions;beginifX < 0.0thenY := 0.0; Success := False;elseY := Sqrt (X); Success := True;endif;return;endSquare_Root;beginSquare_Root (Y => C, X => A ** 2 + B ** 2, Success => Success);ifSuccessthenT_IO.Put ("C is "); F_IO.Put (Item => C);elseT_IO.Put ("C cannot be calculated!");endif;endError_Handling_2;
Ada 到 Ada 2005 的一個限制是函式不能有 out 引數。(函式可以有任何副作用,但不能顯示它們)。因此,對於我們的示例,我們不得不使用過程。壞訊息是 Success 引數值很容易被忽略。
在 Ada 2012 中,函式可以具有任何模式的引數;因此這最終是可能的
functionSquare_Root (X :inFloat; Success :outBoolean)returnFloatis...
這種技術在數學計算中看起來不太好;因此也不是一個好主意。
錯誤條件儲存在全域性變數中。然後透過函式直接或間接讀取該變數。
procedureError_Handling_3isFloat_Error : Boolean;functionSquare_Root (X :inFloat)returnFloatisuseAda.Numerics.Elementary_Functions;beginifX < 0.0thenFloat_Error := True;return0.0;elsereturnSqrt (X);endif;endSquare_Root;beginFloat_Error := False; -- reset the indicator before use C := Square_Root (A ** 2 + B ** 2);ifFloat_ErrorthenT_IO.Put ("C cannot be calculated!");elseT_IO.Put ("C is "); F_IO.Put (Item => C, Fore => F_IO.Default_Fore, Aft => F_IO.Default_Aft, Exp => F_IO.Default_Exp);endif;endError_Handling_3;
從原始碼中可以看出,此技術的麻煩之處在於選擇重置標誌的位置。您可以讓被呼叫者或呼叫者執行此操作。
此外,這種技術不適合多執行緒。
對於像這樣的情況使用全域性變數確實是一個非常糟糕的想法,實際上是其中最糟糕的一種。
Ada 支援一種錯誤處理形式,這種形式長期以來被其他語言使用,例如經典的ON ERROR GOTO ...(來自早期 Basic 方言)到try ... catch 異常處理(來自現代面向物件的語言)。
這個想法是:您將程式的某個部分註冊為錯誤處理程式,以便在發生錯誤時呼叫。您甚至可以定義多個處理程式來分別處理不同型別的錯誤。一旦發生錯誤,執行就會跳轉到錯誤處理程式並在那裡繼續;無法返回到發生錯誤的位置。
這就是 Ada 的方式!
procedureError_Handling_4isRoot_Error:exception;functionSquare_Root (X :inFloat)returnFloatisuseAda.Numerics.Elementary_Functions;beginifX < 0.0thenraiseRoot_Error;elsereturnSqrt (X);endif;endSquare_Root;beginC := Square_Root (A ** 2 + B ** 2); T_IO.Put ("C is "); F_IO.Put (Item => C, Fore => F_IO.Default_Fore, Aft => F_IO.Default_Aft, Exp => F_IO.Default_Exp);exceptionwhenRoot_Error => T_IO.Put ("C cannot be calculated!");endError_Handling_4;
異常處理的強大之處在於它可以在一個異常處理程式中阻止多個操作。這減輕了錯誤處理的負擔,因為不必獨立檢查每個函式或過程呼叫以確保執行成功。
在契約式設計 (DbC) 中,必須使用正確的引數呼叫操作。這是呼叫者對契約的部分。如果實際引數的子型別與形式引數的子型別匹配,並且如果實際引數具有使函式的前置條件為真的值,則子程式將有機會滿足其後置條件。否則會發生錯誤條件。
現在您可能想知道這將如何運作。讓我們先看看示例
procedureError_Handling_5issubtypeSquare_Root_TypeisFloatrange0.0 .. Float'Last;functionSquare_Root (X :inSquare_Root_Type)returnSquare_Root_TypeisuseAda.Numerics.Elementary_Functions;beginreturnSqrt (X);endSquare_Root;beginC := Square_Root (A ** 2 + B ** 2); T_IO.Put ("C is "); F_IO.Put (Item => C, Fore => F_IO.Default_Fore, Aft => F_IO.Default_Aft, Exp => F_IO.Default_Exp);return;endError_Handling_5;
如您所見,該函式需要一個X >= 0 的前置條件——也就是說,該函式只能在X ≥ 0 時呼叫。作為回報,該函式承諾作為後置條件,返回值也 ≥ 0。
在一個完整的 DbC 方法中,後置條件將陳述一個關係,該關係完全描述了執行函式時產生的值,例如result ≥ 0 and X = result * result。此後置條件是√對契約的部分。使用斷言、註釋或語言型別系統來表達前置條件X >= 0 體現了契約式設計的兩個重要方面
- 可能存在編譯器或分析工具可以幫助檢查契約的方法。(例如,當
X ≥ 0來自 X 的子型別,並且呼叫時√的引數是相同的子型別,因此也 ≥ 0 時,就會出現這種情況。) - 可以在呼叫函式之前機械地檢查前置條件。
第一個方面增加了安全性:沒有程式設計師是完美的。每個需要由程式設計師自己檢查的契約部分都有很高的出錯機率。
第二個最佳化方面非常重要——當合約可以在編譯時進行檢查時,就不需要執行時檢查。你可能沒有注意到,但如果你仔細想想: 永遠不會為負數,前提是指數運算子和加法運算子以通常的方式工作。
我們已經為一段永遠不會失敗的程式碼提供了 5 個不錯的錯誤處理示例。這為控制 DbC 的一些執行時方面提供了絕佳的機會:現在你可以安全地關閉檢查,程式碼最佳化器可以省略實際的範圍檢查。
DbC 語言在合約違反時如何表現方面有所區別。
- 真正的 DbC 程式語言將 DbC 與異常處理結合起來——在執行時檢測到合約違反時丟擲異常,並提供重新啟動失敗例程或以已知良好狀態阻塞的方法。
- 靜態分析工具在分析時檢查所有合約,並要求程式碼以一種永遠不會在執行時違反合約的方式編寫。
Ada 2012 引入了前置條件和後置條件方面。