Defensive Programming 防禦式編程(Defensive Programming)

Defensive Programming

防禦式編程(Defensive Programming)是提高軟件質量技術的有益輔助手段

怎麼理解呢?防禦式編程思想的理解可以參考防禦式駕駛:

在防禦式駕駛中要建立這樣一種思維,那就是你永遠也不能確定另一位司機將要做什麼。這樣才能確保在其他人做出危險動作時你也不會受到傷害。你要承擔起保護自己的責任,哪怕是其他司機犯的錯誤。

防禦式編程的主要思想是:

子程序應該不因傳入錯誤數據而被破壞,哪怕是由其他子程序產生的錯誤數據。

更一般地說,其核心想法是要承認程序都會有問題,都需要被修改,聰明的程序員應該根據這一點來編程序,這種思想是將可能出現的錯誤造成的影響控制在有限的範圍內。

保護程序免遭非法輸入數據的破壞

計算機領域有着一句GIGO(Garbage In Garbage Out)俗語,翻譯過來就是垃圾進,垃圾出,意思就是有垃圾數據進來後,出來的也是垃圾數據

而就目前而言,對於已經成型的產品可能單單是這種原則並不適用,而是應該做到垃圾進,什麼也不出垃圾進,出去的是錯誤提示垃圾進,經過篩選提取,出去的是有用信息或是不許垃圾進來。換句話說,GIGO於今天的標準看來已然是差勁程序的標誌了。

防禦式編程針對垃圾進這種情況,有以下三種方法處理:

1、檢查所有來源於外部的數據

當從文件、用戶、網絡或其他外部接口中獲取數據時,應檢查所獲得的數據值,以確保它在允許的範圍內。

2、檢查子程序所有輸入參數的值

檢查子程序輸入參數的值,事實上和檢查來源於外部的數據一樣,只不過數據來源於其他子程序而非外部接口。

3、決定如何處理錯誤的輸入數據

一旦檢測到非法的參數,你該如何處理它呢?根據情況的不同,你可以選擇適合你的錯誤處理技術斷言來處理。

接下來我們將針對以上所說的情況講解防禦式編程中需要掌握的方式:

斷言

斷言是指在開發期間使用的、讓程序在運行時進行自檢的代碼(通常爲宏或一個子程序)。斷言爲真則程序正常運行,斷言爲假則意味着代碼中發生了錯誤。

舉個例子:一份客戶信息程序要求包含記錄數不超過10,我們加一個斷言。當記錄數小於10,斷言會默默無語兩眼淚,當超過10,斷言就會大聲的說程序中存在一個錯誤!

斷言對於大型複雜程序或可靠性要求極高的程序來說尤爲重要。通過使用斷言,程序員能更快速排查出因修改代碼或者別的原因,而弄進程序裏不匹配的接口和錯誤等。

OC中內置的斷言:(iOS每個線程都可以指定斷言處理器。想設置一個 NSAssertionHandler 的子類來處理失敗的斷言,在線程的 threadDictionary 對象中設置 NSAssertionHandlerKey 字段即可

對NSAssertionHandler有興趣的童鞋請移步:傳送門

#define NSAssert(condition, desc, ...)  \
    do {                \
    __PRAGMA_PUSH_NO_EXTRA_ARG_WARNINGS \
    if (__builtin_expect(!(condition), 0)) {        \
            NSString *__assert_file__ = [NSString stringWithUTF8String:__FILE__]; \
            __assert_file__ = __assert_file__ ? __assert_file__ : @"<Unknown File>"; \
        [[NSAssertionHandler currentHandler] handleFailureInMethod:_cmd \
        object:self file:__assert_file__ \
            lineNumber:__LINE__ description:(desc), ##__VA_ARGS__]; \
    }               \
        __PRAGMA_POP_NO_EXTRA_ARG_WARNINGS \
    } while(0)
#endif

下面將介紹一下斷言使用時的建議:

1、建立自己的斷言機制

很多時候,系統自帶的斷言無法滿足我們的需求,比如iOS斷言在release模式下會失效,那麼我們可以自定義斷言來適應我們的項目

下面是C++的斷言宏示例:


#define ASSERT(condition, message) {    \
    if (!condition) {                   \
        Log("ERROR ",condition,message);\
        exit( EXIT_FAILURE );           \
    }                                   \
}                                       \

OC中示例:

#define WYHAssert(condition, desc)  \
if (DEBUG) {                        \
   NSAssert(condition, desc);       \
}else {                             \
   NSString *app_build = [[NSBundle mainBundle].infoDictionary objectForKey:@"CFBundleVersion"]; \
NSLog(@"Assert Error condition:%@ (desc: %@) \n Occur in <%s><第%d行> , AppBuildVersion:%@",condition,desc,__FILE__,__LINE__,app_build); \
   [LogModule postLog];                            \
} \

2、用錯誤處理代碼處理預期發生的狀況,用斷言去處理那些不可發生的錯誤!

斷言和錯誤處理代碼的區別:

斷言是用來檢查永遠不該發生的情況,而錯誤處理代碼(error-handling code)是用來檢查不太可能經常發生的情況,這些情況是能在寫代碼時被預料的,且在產品正式上線時也要處理這些情況,因而說錯誤處理通常用來檢查有害的輸入數據,而斷言是用於檢查代碼中的bug !

有種方式可以讓你更好理解斷言:

把斷言看做是可執行的註解,你不能依賴它來讓代碼正常工作,但與編程語言中的註解相比,它更能主動地對程序中的假定做出說明。

3、利用斷言來註解前條件和後條件

前條件(先驗條件)和後條件(後驗條件)專有名詞最初來自於契約式設計(Design by Contract)(DbC),使用契約式設計時,每個子程序或類與程序的其餘部分都形成了一份契約。

很多語言都有對這種斷言的支持。然而DbC認爲這些契約對於軟件的正確性至關重要,它們應當是設計過程的一部分。實際上,DbC提倡首先寫斷言。(百度百科)

前條件:子程序或類的調用方代碼再調用子程序或實例化對象之前要確保爲真的屬性。前條件是調用方對其所調用的代碼要承擔的義務。

後條件:子程序或類在執行結束後要確保爲真的屬性,後條件是子程序或類對調用方代碼所承擔的責任。

而斷言是用來說明前後條件的有利工具。

下面舉個例子說明:

/// 警報站座標
private class EStationCoordinate: NSObject {

    var latitude: Float?
    
    var longitude: Float?
    
    var elevation: Float = 0.0
    
    init(_ latitude: Float,_ longitude: Float,_ elevation: Float) {
        super.init()
        
        self.latitude = latitude
        self.longitude = longitude
        self.elevation = elevation
    }
}


/// 取得報警站座標
///
/// - Parameters:
///   - latitude: <#latitude description#>
///   - longitude: <#longitude description#>
///   - elevation: <#elevation description#>
/// - Returns: <#return value description#>
private func createEmergencyCoordinate(_ latitude: Float,_ longitude: Float,_ elevation: Float) -> EStationCoordinate {
    
    // precondition
    assert(-90 <= latitude && latitude <= 90, "latitude must within range !");
    
    assert(0 <= longitude && longitude < 360, "longitude must within range !");
    
    assert(100 <= elevation && elevation < 500, "elevation must within range !");
    
    // handle .... searching in local
    
    // postcondition
    assert(isContain, "local not contain this coordinate !")
    
    var coordinate = EStationCoordinate(latitude,longitude,elevation)
    
    return coordinate
    
}

如果變量latitude、longitude和elevation都是來源於系統外部,那麼就應該用錯誤處理代碼來檢查和處理非法的數值,而如果變量的值是源於可信的系統內部,並且這段程序是基於這些值不會超出合法範圍的假定而設計,使用斷言則是非常合適的。

4、避免將需要執行的子程序放到斷言中

如果把需要執行的子程序代碼寫在斷言的codition條件裏,那麼當你關閉斷言功能時,編譯器很可能就把這些代碼排除在外了,下面舉一個例子:

- (void)postFileToServer {
    
    // .... make file
    
    NSAssert([self compressFileToZip], @"File can't be compressed !");
    
    // ... post to server 
}

- (BOOL)compressFileToZip {
    
    //... compress file and create a zip path !
    if (zipPath.length > 0) {
        
        return YES;
    }
    return NO;
}

這樣如果未編譯斷言,則condition語句的子程序也將不會執行,應修改爲以下:

- (void)postFileToServer {
    
    // .... make file
    BOOL isCompressSuccess = [self compressFileToZip];
    
    NSAssert(isCompressSuccess, @"File can't be compressed !");
    
    // ... post to server
}

錯誤處理技術

前面我們提過了,斷言是處理程序代碼中那些不應發生的錯誤,那麼又如何處理那些我們預料之內的可能發生的錯誤呢?

首先我們要明確對於程序而言,處理錯誤最恰當的方式是要根據程序軟件的類別而定,進而言之就是對於程序的兩個概念:健壯性與正確性

程序的健壯性:健壯性具體指的是系統在不正常的輸入或不正常的外部環境下仍能表現出正常的程度。

健壯性的原則:

  • 不斷嘗試採取措施來包容錯誤的輸入以此讓程序正常運轉(對自己的代碼要保守,對用戶的行爲要開放)
  • 考慮各種各樣的極端情況,沒有impossible
  • 即使終止執行,也要準確/無歧義的向用戶展示全面的錯誤信息
  • 錯誤信息有助於進行debug

例如:視頻遊戲中的繪圖子程序接收到了一個錯誤的顏色輸入,那麼在設計的時候可以針對這種情況,採用它的默認背景色或前景色繼續繪製,而不是讓程序崩潰或退出。

程序的正確性:正確性意味着程序永不返回不準確的結果,即使這樣做會不返回結果或是直接退出程序。

例如:在設計控制治療癌症病人的放療設備的軟件時,當軟件接收到錯誤的放射劑量,那麼也許直接關閉程序就是最佳的選擇,哪怕重啓也比冒險施放錯誤的放射劑量要好的多。

總結,兩者之間的區別在於:

  • 正確性:永不給用戶錯誤的結果,哪怕是退出程序
  • 健壯性:儘可能的保持軟件運行而不是總是退出

瞭解了程序的健壯性與正確性,我們就可以採用以下幾種手段,或結合起來使用錯誤處理技術:

1、返回中立值:

有時,處理錯誤的最佳做法就是繼續執行操作並簡單的返回一個沒有危害的值。

比如,一個基於輸入顏色的繪圖子程序接收到了一個錯誤的顏色輸入,它可以忽略這個錯誤的顏色,而是採用默認的底色或前景色繼續進行繪製,而不是直接崩潰。

2、換用下一個正確的數據

在處理輪詢查詢狀態的子程序時,如果某次查詢出的輸出數據錯誤或有誤,大可以忽略本次錯誤的數據,繼續等待下一次輪詢時讀取正確的數據(例如,如果你以每秒100次的速度讀取體溫計的數據,如果某一次得到的數據有誤,我們可以再等上1/100秒後繼續讀取正確的數據)

3、返回與前次相同的數據

還是舉上一個例子,如果體溫計在1/100秒讀取到的是一個錯誤數據,那麼大可以返回上一次正確的數據,因爲溫度在1/100秒內變化不會太大。

4、換用最接近的合法值

比如,當我們在編寫一個滑塊在規定區域內滑動的程序時,如果滑塊超過規定區域,我們可以取最接近於超過區域的安全數值返回。

5、把警告信息記錄到日誌文件中

在檢測到錯誤數據時,可以選擇在日誌文件中記錄一條警告信息,然後繼續執行。

6、返回一個錯誤狀態碼

可以決定只讓系統的某些部分處理錯誤,其他部分則不在局部處理錯誤,而是簡單的返回一個錯誤碼。

比如在用戶信息編輯頁面有一個保存按鈕,當某些信息填寫錯誤時,這時只是記錄一個錯誤碼,當點擊保存按鈕時纔去判斷驗證這個錯誤碼是否存在,決定是否允許用戶執行下一步操作

7、調用錯誤處理子程序或對象

把錯誤處理都集中在一個全局的錯誤處理子程序或對象中,這種方法優點在於能把錯誤處理的職責集中到一起,從而讓調試變得更簡單。而代價則是整個程序都要知道這個集中點,並與之緊密耦合。

什麼意思呢?比如在一系列有上下文關係的請求中,針對所有的請求錯誤,我們只封裝一個錯誤管理類來集中管理這些錯誤。

8、當錯誤發生時顯示出錯消息

這種方法可以把錯誤處理的開銷減到最小,然而你需要衡量此時的錯誤消息對於用戶而言是否是友善的,相反對於攻擊者而言,儘量不要讓他們利用錯誤信息來發現如何攻擊這個系統。

9、關閉程序

有一些更偏向於正確性的程序,當檢測到錯誤發生時,也許關閉程序是最佳的選擇。

如上面談到的癌症病人的放療設備的軟件

異常

異常是把代碼中的錯誤或異常事件傳遞給調用方代碼的一種特殊手段。

異常處理,英文名爲exceptional handling, 是代替日漸衰落的error code方法的新法,提供error code 所未能具體的優勢。異常處理分離了接收和處理錯誤代碼。這個功能理清了編程者的思緒,也幫助代碼增強了可讀性,方便了維護者的閱讀和理解。 異常處理(又稱爲錯誤處理)功能提供了處理程序運行時出現的任何意外或異常情況的方法。異常處理使用 try、catch 和 finally 關鍵字來嘗試可能未成功的操作,處理失敗,以及在事後清理資源。(百度百科)

如果在一個子程序中遇到了預料之外的情況,但並不知道如何處理的話,你就可以選擇拋出一個異常。

異常的基本結構是:子程序使用throw拋出一個異常對象,再被調用鏈上層其他子程序的try-catch語句捕獲。(內建的異常機制都是沿着函數調用棧的函數調用逆向搜索,直到遇到異常處理代碼爲止)

我知道聽到這,肯定有人懵逼了,我們來看下面的例子:

+ (void)tryFirstException {
    @try {
        // 1
        [self trySecondException];
        
    } @catch (NSException *exception) {
        //2
        NSLog(@"First reason:%@",exception.reason);
        
    } @finally {
        //3
    }
    //4
}

+ (void)trySecondException {
    @try {
        //5
        [self tryThirldException];
        
    } @catch (NSException *exception) {
        //6
        @throw exception; //如果將這段代碼註釋後又會怎樣?
        NSLog(@"Second reason:%@",exception.reason);
    } @finally {
        //7
    }
    //8
}

+ (void)tryThirldException {
    //9
    @throw [NSException exceptionWithName:@"Exc" reason:@"no reason!" userInfo:nil];
}

有人知道程序應該怎麼執行嗎?

許多常見的程序設計語言,包括Actionscript,Ada,BlitzMax,C++,C#,D,ECMAScript,Eiffel,Java,ML,Object Pascal(如Delphi,Free Pascal等),Objective-C,Ocaml,PHP(version 5),PL/1,Prolog,Python,REALbasic,Ruby,Visual Prolog以及大多數.NET程序設計語言,內建的異常機制都是沿着函數調用棧的函數調用逆向搜索,直到遇到異常處理代碼爲止。一般在這個異常處理代碼的搜索過程中逐級完成棧捲回(stack unwinding)。但Common Lisp是個例外,它不採取棧捲回,因此允許異常處理完後在拋出異常的代碼處原地恢復執行。

下面將給予一些使用異常的建議:

1、用異常通知程序的其他部分,發生了不可忽略的錯誤

異常機制的優越之處,在於它能提供一種無法被忽略的錯誤通知機制。其他錯誤處理技術有可能會導致錯誤在不知不覺中向外擴散,而異常則消除了這種可能性。

2、只在真正例外的情況下才拋出異常

僅在真正例外的情況下才使用異常——換句話說,就是僅在其他編碼實踐方法無法解決的情況下才使用異常。這種情況跟斷言相似——都是用來處理那些不僅罕見甚至永遠不該發生的情況。

3、不能用異常來推卸責任

如果某種錯誤情況可以在局部處理,那就應該在局部處理它。不要把本可以處理的錯誤當成一個未被捕獲的異常拋出去。

4、避免在構造函數和析構函數中拋出異常,除非你在同一個地方把它們捕獲

如果嘗試在構造函數或析構函數中拋出異常,則處理異常將變得非常複雜。
比如在C++中,只有對象在完全構造之後才能調用析構函數,也就是說如果再構造函數中拋出異常,就不會調用析構函數,從而造成潛在的資源泄漏。

5、在恰當的抽象層次拋出異常

當你決定把一個異常傳給調用方時,請確保異常的抽象層次與子程序的接口抽象層次一致。
(比如在A類的某一子程序中,有一個getDefenseId的方法,在方法中的某些步驟中,我們拋出了一個文件讀寫錯誤的異常,這本應由層次更低的內部類F專職去做的事,卻錯誤的在A類中拋出異常,破壞了封裝性,也暴露了一些私有的實現細節,這顯然不是我們想要的)

6、在異常消息中加入關於導致異常發生的全部信息

比如因爲一個索引值錯誤而拋出的,就應該在異常消息中包含索引的上下界限值、非法的索引下標值等信息。

7、避免使用空的catch語句

不要視圖敷衍一個不知該如何處理的異常

8、考慮創建一個集中的異常報告機制

封裝一個異常報告的子程序(或基類)專門快速方便的報告程序中需要主動拋出的異常(異常處理器),將對於異常的使用更加標準化

9、考慮異常的替換機制

雖然一些編程語言對於異常的支持已有5~10年甚至更久的歷史,但關於如何安全使用異常的經驗仍然還是很少。

拿iOS舉例,Apple.inc雖然同時提供了錯誤處理(NSError)和異常處理(Exception)兩種機制,但是Apple不建議我們主動去使用Exception,更加提倡開發者使用NSError來處理程序運行中可恢復的錯誤。而異常被推薦用來處理不可恢復的錯誤。

Important: In many environments, use of exceptions is fairly commonplace. For example, you might throw an exception to signal that a routine could not execute normally—such as when a file is missing or data could not be parsed correctly. Exceptions are resource-intensive in Objective-C. You should not use exceptions for general flow-control, or simply to signify errors. Instead you should use the return value of a method or function to indicate that an error has occurred, and provide information about the problem in an error object. For more information, see Error Handling Programming Guide.

(developer.apple.com)

感興趣的童鞋請移步蘋果官網 傳送門

多說一句,雖然apple不推薦我們經常主動使用Exception,但針對於crash的異常捕捉,iOS是可以通過NSSetUncaughtExceptionHandler來捕獲大部分crash的,但值得注意的是無法捕獲那些由於內存溢出野指針BAD_ACCESS導致的crash,比如Flurry中對crash處理就是這麼運作的。

    - (void) uncaughtExceptionHandler(NSException *exception) 
    {
        [Flurry logError:@"Uncaught" message:@"Crash!" exception:exception];
    }
 
    - (void)applicationDidFinishLaunching:(UIApplication *)application
    {
        NSSetUncaughtExceptionHandler(&uncaughtExceptionHandler);
        [Flurry startSession:@"YOUR_API_KEY"];
        // ....
    }

隔欄

隔欄是防禦式編程中的一種容損策略,舉個例子,大家可以這樣來理解:

船體外殼上裝備隔離艙,如果船隻與冰山相撞導致船體破裂,隔離艙就會被封閉起來,從而保護船體的其餘部分不會受到影響。
隔欄過去叫防火牆,但現在防火牆這一術語常用來代指阻止惡意網絡攻擊)

如下圖:

5毛錢特效翻譯過來就是:

左側外部接口數據假定是骯髒的不可信的,中間這些類(子程序)構成隔欄,負責清理和驗證數據並返回可信的數據,最右側的類(子程序)全部在假定數據乾淨(安全)的基礎上工作,這樣可以讓大部分的代碼無須再擔負檢查錯誤數據的職責!

這種策略可以擬一個比較生動的例子,可以看做是手術室裏使用的一種策略。
任何東西在允許進入手術室之前都要經過消毒處理,因此手術室裏的任何東西都可以認爲是安全的。這其中最核心的設計決策是規定什麼可以進入手術室,什麼不可以,還有把手術室的門設在哪裏。

在編程中也是如此,約定哪些子程序可認爲是在安全區域裏的,哪些又是在安全區域外的,哪些負責清理數據(完成這一工作最簡單的方法是在得到外部數據時,立即進行清理,不過數據往往需要經過一層以上的清理,因此多層清理有時也是必須的)

隔欄的應用:

隔欄的使用使斷言和錯誤處理有了清晰的區分,隔欄外部的程序應該使用錯誤處理技術,在那裏對數據做的任何處理假定都是不安全的。而隔欄內部的程序裏就應該使用斷言技術,因爲傳進來的數據應該已在通過隔欄時被清理過了。如果隔欄內部的子程序檢測到了錯誤的數據,那麼應該是程序裏的錯誤而不是程序外的錯誤。

輔助調試代碼

防禦式編程的另一重要方面就是使用調試助手(輔助調試代碼),調試助手非常之強大👍,可以幫助我們快速檢查錯誤。

應用在開發期間應犧牲一些速度和對資源的使用,來換取一些可以讓開發更順暢的內置工具。

1、應儘早的引入輔助調試代碼

越早進入輔助調試代碼,它能夠提供的幫助也越大。如果你經常遇到某些問題,嘗試自己編寫或引入一些輔助調試代碼,它就會自始至終在項目中幫助你。

2、採用進攻式編程

什麼又是進攻式編程,其實這也是防禦式編程中的一種習慣,其思想在於:

儘量讓異常的情況在開發期間暴露出來,而在產品上線時自我恢復。

比如你有一段switch case語句用來處理事件,在開發期間應儘量考慮所有你能預料得到的情況並作出處理,另外在default case語句中,如果是開發階段可以採用進攻式編程處理,而在產品正式上線期間,針對default case應更穩妥一些,比如記錄錯誤日誌。

下面列舉一下進攻式編程的方法:

  • 確保斷言語句使程序終止運行
  • 完全填充分配到的所有內存
  • 完全填充己分配到的所有文件或流
  • 確保每一個case 語句中的default分支或else 分支都能產生嚴重錯誤(如終止程序)
  • 在刪除一個對象之前把它填滿垃圾數據
  • 讓程序將錯誤日誌主動用電子郵件或推送發送給開發者(安防目前採用)

3、計劃移除調試輔助的代碼

如果是商用軟件,調試用的代碼有時會使軟件的體積變大且速度變慢,從而給程序造成巨大的性能影響。要事先做好準備計劃,避免調試用的代碼和程序原代碼糾纏不清,下面列舉一些可以選擇的移除方法:

  • 使用類似ant和make這樣的版本控制工具
    (可以從同一套源碼編譯出不同版本的程序。在開發模式下,你可以讓make工具把所有的調試代碼都包含進來一起編譯。而在產品上線期間,把那些調試代碼排除在外。)

  • 使用內置的預處理器(C++ #字符爲預處理器指令,包含#if、#warning、#include、#define等
    (如果你所用的編程環境裏有預處理器,你可以用預處理器來包含或排除調試的代碼)

- (void)handleSomething {
    
#ifdef DEBUG
    
    [self debugF];//通常爲一些debug用的耗時操作
#else
    
    [self releaseF];
#endif
    
}
  • 爲應用增加調試模式的入口

如果你的應用需要同時支持兩種模式(發佈和調試),那麼我們可以自定義進入調試模式的入口,而不是針對編譯層次的DEBUG,我們的調試代碼的嵌入也依賴於這個調試模式是否開啓,下面將演示安防app內定義的調試模式。

對防禦式編程採取防禦的姿態

說了這麼多,那麼是不是防禦式代碼越多越好呢?

其實也不是,過度的防禦式編程也會引起問題,如果你在每一個能想到的地方都用每一種能想到的方法來檢查參數、處理錯誤,那麼你的程序會變得臃腫而緩慢,更糟的是,過度的防禦式代碼換來的是軟件的複雜度。

這說明,防禦式編程引入的代碼也並非不會有缺陷,和其他代碼一樣,你同樣能輕而易舉的在防禦式編程添加的代碼中找到錯誤,尤其是當你隨手編寫這些代碼時更是如此。

因此,要考慮好什麼地方需要進行防禦,然後因地制宜地調整你進行防禦式編程的優先級。

總結

  • 程序代碼中對錯誤處理的方式遠比GIGO複雜的多。

  • 防禦式編程技術可以讓錯誤更容易發現問題、更容易修改,並減少錯誤對產品代碼的破壞。

  • 遵從防禦式編程的思想去開發,會讓你在開發階段就提前處理了許多問題,提高代碼質量,降低測試周期,要做到主動去發現錯誤並做出處理(千萬不要存僥倖心理,明明可以多考慮幾種情況,偏偏卻要忽略它們的可能性),而不是等到bug隱式的出現所帶來的未曾預料的災難。

  • 錯誤處理技術更適用於暴露的接口程序或類,而斷言則更強調不可允許的錯誤,多適用於不暴露在外的私有方法(或內部類)。

  • 處理錯誤最恰當的方式是要根據程序軟件的類別而定,更傾向於正確性還是健壯性

  • 異常提供了一種與代碼正常流程角度不同的錯誤處理手段,但同時也應該在異常和其他錯誤處理手段之間進行權衡比較,比如iOS中就很少採用異常處理機制。

  • 合理的運用輔助調試代碼,會讓你不管是在開發還是發佈階段都能更快速定位問題,並從容地解決問題。(預處理器就是個很好的選擇)

最後,我對於防禦式編程的理解是,我認爲程序的好壞與其健壯性(和正確性)有很大的聯繫,所有的程序開發人員都要對它有足夠的重視,主動去迎戰錯誤,從一點一滴開始做起,不要忽視任何的細節,不能盲目依賴測試去發現bug,而是以測試驅動編程,不斷地思考可能發生的問題以進行預防,做一個聰明的程序員。這纔是防禦式編程所告訴我們的事 !

推薦

最後列舉一下文中出現的引用來源及一些推薦看的文章或書籍:

  • 《代碼大全第2版》第八章防禦式編程

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章