防禦性程式設計

防禦性編程(Defensive programming)是防禦式設計的一種具體體現,它是為了保證,對程序的不可預見的使用,不會造成程序功能上的損壞。它可以被看作是為了減少或消除墨菲定律效力的想法。防禦式編程主要用於可能被濫用,惡作劇或無意地造成災難性影響的程序上。[1]

主要概念:若常式收到錯誤的資料,即便錯誤資料是由其他常式出錯所造成,此常式也不會受到傷害。[2]

防禦性編程通常通過以下途徑,從而提高軟件和源碼的質量:

  • 提高工程質量——減少bug和問題
  • 提高源碼可讀性—— 源碼應該變得可讀且可理解,並且能經受代碼審計
  • 讓軟件能通過預期的行為來處理不可預期的用戶操作。

值得注意的是,過度的防禦性編程可能會預防不可能會發生的錯誤,這樣將導致運行時間與維護的損耗。當源碼中擁有過多異常捕捉和異常處理,這有可能導致結果不正確或者被隱藏。

安全編程

防禦性編程有時也被計算機科學家稱為安全編程(Secure programming)。潛在的軟件缺陷可能會被黑客利用,而進行代碼注入拒絕服務攻擊或其他攻擊。

防禦性編程與非防禦性編程之間的區別在於,程序員不會對特定的函數調用或庫的使用情況做假設。下面是一個例子:

int risky_programming(char *input){
  char str[1000+1];     // 1000为内容,1为空终止字符串
  // ...
  strcpy(str, input);   // 复制输入
  // ...
}

當輸入超過1000個字符時,該函數將會崩潰。一些新手程序員可能並不會覺得這是個問題,因為沒有用戶會輸入這麼長的字符串。實行防禦性編程的程序員不會允許這樣的錯誤,因為這段程序包含一個已知的bug,一個可能會導致緩衝區溢出攻擊的漏洞。下面這個例子是一個解決方案:

int secure_programming(char *input){
   char str[1000+1];  // 1000为内容,1为空终止字符串
   
  // ...
  
  // 限制越界拷贝。
  strncpy(str, input, sizeof(str)); 
 
  // 当最后一个字符不为空终止字符串,我们可以选择只处理未越界的内容,或者直接停止程序
  str[sizeof(str) - 1] = '\0';
  // ...
}

進攻式編程

進攻式編程(Offensive programming )是防禦式編程( defensive programming)的子集,其思想為:在重點的代碼處不應該一些錯誤進行處理。在實踐中,只有來自於軟件外部的錯誤應該被處理(如:用戶的輸入);理論上,軟件自身和相關可控的數據是可信的。

可信內部數據的校驗

過度防禦式編程

const char* trafficlight_colorname(enum trafficlight_color c) {
    switch (c) {
        case TRAFFICLIGHT_RED:    return "red";
        case TRAFFICLIGHT_YELLOW: return "yellow";
        case TRAFFICLIGHT_GREEN:  return "green";
    }
    return "black"; //万一发生其他输入值时,使用信号灯将不亮
}

進攻式編程

const char* trafficlight_colorname(enum trafficlight_color c) {
    switch (c) {
        case TRAFFICLIGHT_RED:    return "red";
        case TRAFFICLIGHT_YELLOW: return "yellow";
        case TRAFFICLIGHT_GREEN:  return "green";
    }
    assert(0); // 认为这种情况不可能发生
}

可信的軟件組件

過度防禦式編程

if (is_legacy_compatible(user_config)) {
    // 策略:新代码可能不完全兼容“用户配置”
    new_code(user_config);
} else {
    // 策略:新代码操作“用户配置”,可能出会错
    // 一旦出错时,,使用旧代码处理
    if (new_code(user_config) != OK) {
        old_code(user_config);
    }
}

進攻式編程

// 完全信任新代码
new_code(user_config);

技術層面

下面是常見的防禦式編程的技術。

理性地重用代碼

如果源碼是廣為利用地且經得起測試的,那麼重用代碼將有助於減少bug。

然而,重用代碼往往不是最好的方案,越是底層的代碼(往往指框架),重用代碼帶來的潛在危害就越是大。重用過於複雜的代碼將大大地增加代碼的複雜度。

遺留代碼

在重用舊代碼、包、API、配置等事物前,我們必須思考這些事物是否值得被重用,思考這些是否會造成遺留問題。

遺留問題的產生,往往是因為舊的設計無法適應當今的需求,尤其舊的設計往往不是為了當今需求,因此開發和測試可能存在缺陷。

參見

參考文獻

  1. ^ 1.3 什么是防御性编程?. 51CTO.com. 2008-08-29 [2013-03-16]. (原始內容存檔於2013-04-06). 
  2. ^ Steve McConnell. 軟體建構之道. ISBN 9789866800115.