防禦式編程

防禦式編程是提高軟體質量技術的有益輔助手段。防禦式編程的主要思想是:子程式應該不因傳入錯誤數據而被破壞,哪怕是由其他子程式產生的錯誤數據。這種思想是將可能出現的錯誤造成的影響控制在有限的範圍內。

基本介紹

  • 中文名:防禦式編程
  • 含義:提高軟體質量技術的有益輔助手段
  • 主要思想子程式不因傳入錯誤數據而被破壞
  • 性質:編程方式
基本釋義,相關實例,

基本釋義

防禦式編程是提高軟體質量技術的有益輔助手段。防禦式編程的主要思想是:子程式應該不因傳入錯誤數據而被破壞,哪怕是由其他子程式產生的錯誤數據。這種思想是將可能出現的錯誤造成的影響控制在有限的範圍內。

相關實例

1. 在非法輸入(Invalid Inputs)中保護你的程式
一個好程式,在非法輸入的情況下,要么什麼都不輸出,要么輸出錯誤信息。有幾種方法來防止非法的輸入:
(1)檢查來自於外部資源(external sources)的所有數據的值,例如來源於網路的數據的值,來源於檔案的數據的值。檢查的目的是保證數據值在一個允許的範圍內。
(2)檢查每一個例程(routine)的輸入參數值。
一旦非法輸入被發現,那么應該根據情況進行處理。防禦式編程的最佳的形式是在一開始就不引入錯誤。
2. 斷言(Assertions)
一個斷言通常是一個例程(routines)或者一個宏(marcos)。每個斷言通常含有兩個參數:一個布爾表示式(a boolean expression)和一個訊息(a message)。一個布爾表達式的反面表示了一個錯誤。C 標準庫提供了一個 assert 宏,它只帶有一個參數,用法如下:
assert(1 == 0); // 注意 boolean expression 不要加引號
使用 assert 宏,需要包含頭檔案 cassert 或者assert.h,執行上面語句的結果是程式終止運行,輸出與下面訊息類似的訊息:
Assertion failed: 1 == 0, file d:\我的文檔\visual studio projects\learning\assert\assert.cpp, line 9
通常來說,我們會定義自己的 assert 宏,其目的有兩個:
(1)新增參數,例如新增一個訊息參數,使得 assert 宏輸出更為豐富的信息。
(2)改變 assert 的行為內容。C 標準庫中的 assert 宏將中斷程式,實際上,我們可以讓程式繼續運行而不中斷或者進入調試狀態等,另外還可以控制訊息輸出的目標,即控制訊息是輸出到控制台還是文本檔案,甚至是通過網路發出。
下面是一個 C++ 實現的斷言:
#define Assert(exp, message) \
{ \
if (!(exp)) \
{ \
std::cout << "Assertion failed: " << #exp << "\n" \
<< "Message: " << message << "\n" \
<< "line: " << __LINE__ << "\n" \
<< "file: " << __FILE__ << "\n"; \
exit(EXIT_FAILURE); \
} \
}
#else
#define Assert(exp, message)
執行 Assert(1 == 0, "Error"); 結果為:
Assertion failed: 1 == 0
Message: Error
line: 24
file: d:\我的文檔\visual studio projects\learning\assert\assert.cpp
使用斷言應該注意一下的幾個問題:
1)對非預期錯誤使用斷言
斷言中的布爾表達式的反面一定要描述一個非預期錯誤,下面所述的在一定情況下為非預期錯誤的一些例子:
(1)空指針
(2)輸入或者輸出參數的值不在預期範圍內。
(3)數組的越界。
非預期錯誤對應的就是預期錯誤,我們通常使用錯誤處理代碼來處理預期錯誤,而使用斷言處理非預期錯誤。在代碼執行過程中,有些錯誤永遠不應該發生,這樣的錯誤是非預期錯誤。斷言可以被看成是一種可執行的注釋,你不能依賴它來讓代碼正常工作(《Code Complete 2》)。例如:
int nRes = f(); // nRes 由 f 函式控制, f 函式保證返回值一定在 -100 ~ 100
Assert(-100 <= nRes && nRes <= 100); // 斷言,一個可執行的注釋
由於 f 函式保證了返回值處於 -100 ~ 100,那么如果出現了 nRes 不在這個範圍的值時,就表明一個非預期錯誤的出現。後面會講到“隔欄”,那時會對斷言有更加深刻的理解。
2)不要把需要執行的代碼放入斷言中
斷言用於軟體的開發和維護,而通常不在發行版本中包含斷言。
需要執行的代碼放入斷言中是不正確的,因為在發行版本中,這些代碼通常不會被執行,例如:
Assert(f()); // f 函式通常在發行版本中不會被執行
而使用如下方法則比較安全:
res = f();
Assert(res); // 安全
3)對來源於內部系統的可靠的數據使用斷言,而不要對外部不可靠的數據使用斷言,對於外部不可靠數據,應該使用錯誤處理代碼。再次強調,把斷言看成可執行的注釋。
前條件(preconditions)和後條件(postconditions)
前條件是調用方代碼在調用例程(routines)或者實例化對象之前要確保為真的條件,後條件是例程執行後或者類實例化後應滿足的條件。下面是一個例子:
// 前條件,這裡 nNum1 和 nNum2 的取值被前面代碼所約束並保證取值在 -50 ~ 50
Assert(-50 <= nNum1 && nNum1 <= 50, "Add_nNum1");
Assert(-50 <= nNum2 && nNum2 <= 50, "Add_nNum2");
int nRes = add(nNum1, nNum2);
// 後條件
Assert(-100 <= nRes && nRes <= 100, "Add_nRes");
注意,由於 nNum1 和 nNum2 取值範圍已經被約束,因此可以使用斷言,但是如果 nNum1 和 nNum2 的值來源於不可靠的外部系統,那么應該使用錯誤處理代碼,而不是使用斷言。
3. 錯誤處理技術
這裡主要講述如何處理預期錯誤。
(1)終止程式運行
有些錯誤非常嚴重,如果出現,那么最好就的做法就是讓程式終止並且讓用戶重啟程式。例如,對於顯示 X 光片的繪圖程式,如果數據出錯,那么就關閉程式,這個時候關閉程式要遠遠好於顯示錯誤的數據。
(2)繼續程式運行
有時候,錯誤出現了,但是沒有必要去關閉程式,那么就有兩種處理方案:
a. 在例程中處理錯誤
例如讓例程返回一個中立值,這是一種可行的方法,中立值在有些語言裡面被描述為“類型的默認值”,例如整型的中立值為 0,指針的中立值為 NULL(或 null 等)
b. 在例程外處理錯誤
返回一個錯誤碼也是可行的,返回錯誤碼意味著,錯誤將交由其他程式部分來處理,而不是本例程處理。
對於出現了錯誤,而沒有終止程式的運行,這時候,你可以在日誌檔案中添加一個警告信息。
抉擇:正確性和健壯性
有些程式要求非常高的正確性,而有些程式要求較高的健壯性,通常兩者我們只能取其一。
(1)正確性意味著結果永遠是正確的,如果出錯,寧願不給出結果也不要給定一個不準確的值。
(2)健壯性意味著通過一些措施,保證軟體能夠正常運行下去,即使有時候會有一些不準確的值出現。
4. 隔欄(barricades)
隔欄本身就是一組錯誤處理代碼,對於內部類只需要使用斷言而無需使用錯誤處理代碼。當斷言為假時,表明了問題出在了程式中而不是數據中,需要通過修改代碼來消除問題。在此,請讀者聯繫本文開始 --- “在非法輸入(Invalid Inputs)中保護你的程式”這一部分進行思考。

相關詞條

熱門詞條

聯絡我們