複製鏈接
請複製以下鏈接發送給好友

防禦式編程

鎖定
防禦式編程是提高軟件質量技術的有益輔助手段,它是一種編程方式。
中文名
防禦式編程
含    義
提高軟件質量技術的有益輔助手段
主要思想
子程序不因傳入錯誤數據而被破壞
性    質
編程方式

防禦式編程基本釋義

防禦式編程的主要思想是:子程序應該不因傳入錯誤數據而被破壞,哪怕是由其他子程序產生的錯誤數據。這種思想是將可能出現的錯誤造成的影響控制在有限的範圍內。

防禦式編程相關實例

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)中保護你的程序”這一部分進行思考。