CODE COMPLETE 2 軟體開發實務指南 第8章 防禦性程式設計

        第八章內容是錯誤處理,防禦性程式設計是文青式語言,本章內容可與《Clean Code》第七章對照看。


防禦性程式設計的主要思維是子程式不因傳入錯誤資料而被破壞。


核心思維是承認程式都會有問題,都需要被修改。


8.1  保護程式免遭非法輸入資料的破壞


 


通常採用三種方式來處理垃圾進來的情形。


 


檢查來自於外部所有的資料值


檢查子程式所有輸入參數的值


檢查來自於外部的資料,差別在於資料來自於子程式而非外部介面。


 


決定如何處理錯誤的輸入資料


8.2  斷言


斷言assertion指在開發期間,程式運行時進行自我檢查的程式碼,通常是一個子程式或macro。


一個斷言通常有兩個參數:一個是布林運算式,描述假設為真的情況,另一則是代表斷言為假時需要顯示的訊息。


斷言可檢查以下假設:


輸入參數或輸出參數的值位於預期範圍內。


子程式開始或結束時,檔案或資料流是處於打開或關閉的狀態。


子程式開始或結束時,檔案或資料流的讀寫位置處於開頭或結尾處。


檔案或資料流已指定採用唯讀、唯寫或可讀可寫的方式打開。


只用來作為輸入的變數值,沒有被子程式修改。


指標非空。


傳入子程式的陣列或其他容器至少能容納X個資料元素。


表格已被初始化,儲存著真實的數值。


子程式開始或結束時,某個容器是空的或滿的。


比較兩種子程式的運算結果是否一致。


 


斷言主要用在開發與維護階段,產品程式碼可將斷言排除,以免降低系統效能。


建立自己的斷言機制


C++、Java 和 VB.NET 都有內建支持斷言。


作者提供一個C++版範例斷言範例。


使用斷言的指引


用錯誤處理程式碼來處理預期會發生的狀況,用斷言來處理絕不該發生的情況


錯誤處理通常是用來檢查有害的輸入資料,而斷言是用來檢查程式的bug。


如果發生異常狀況觸發斷言,應該修改程式的原始碼並重新編譯,然後發佈新的軟體版本。


避免把需要執行的程式碼放到斷言中


斷言可看做是可執行的註解。


兩個範例寫得清楚,斷言有可能會關掉,需要執行的程式碼放到斷言有可能會出現錯誤。


用斷言來注解來注解並驗證前置條件和後置條件


前置條件和後置條件是契約設計的一部分。


前置條件是在呼叫子程式或實例化物件之前要確定為真的屬性。前置條件是呼叫方程式碼對於呼叫程式碼要承擔的義務。


後置條件是子程式或類別在執行結束後要確保為真的屬性。後置條件是子程式或類別對於呼叫方程式碼要承擔的義務。


斷言能夠動態判斷前置條件與後置條件是否為真。


對於高健全性的程式碼,應該先使用斷言,然後再處理錯誤


8.3  錯誤處理技術


返回中立值


有時處理錯誤資料的方法是回傳一個無害的值,不過醫療相關的程式返回中立值不一定是好方法。


換用下一個正確的資料


返回與前次相同的資料


換用最接近的合法值


 


舉例來說合法值是0到100,當得到負數的時候可回傳最接近的合法值0。


把警告訊息記錄到日誌檔中


檢測到錯誤資料時,可以選擇在logfile記錄一條警告訊息,然後繼續執行。


返回一個錯誤碼


通知系統其餘部分此處已發生錯誤,可採取下列方式:


設定一個狀態變數的值


用狀態值作為函式的返回值


與語言內建的例外機制、拋出一個例外


 


要決定系統哪些地方直接處理錯誤,哪些地方只回報錯誤。


呼叫錯誤處理子程式或物件


一種方法是把錯誤處理都集中在一個全域的錯誤處理子程式或物件。


優點:錯誤處理都集中一處,會使除錯工作變簡單。


缺點:整個系統都必須知道這個集中點,會出現緊密耦合。每個子系統會包含錯誤處理程式,較難重複使用。若是出現緩衝區溢出的情況,也會影響除錯。


當錯誤發生時顯示錯誤訊息


錯誤處理的花費最少,要避免透露太多錯誤訊息。


用最妥當的方式在局部處理錯誤


一些方案要求在局部解決所有遭遇的錯誤,優點是靈活缺點是可能會影響整體效能。


關閉程式


與生命安全有關的程式,出現錯誤會關閉程式。


健全性與正確性


 


錯誤處理方式方式有時偏向正確性,有時偏向健全性。


正確性意味永不回傳不正確的結果,不回傳結果也比回傳不準確的結果來得好。


健全性意味要採取措施要保證軟體可以持續運轉下去,可以忍受有時會出現不準確的結果。


人身安全相關的軟體會傾向正確性,一般消費軟體會傾向健全性。


高層設計對錯誤處理方式的影響


決定一種通用的處理錯誤參數處理方法,是高層架構的設計決策。


如果設定高層處理錯誤,低層回報錯誤,就要確保高層程式碼真的有處理錯誤。


防禦性程式設計的重點在於防禦未曾預料到的錯誤。


8.4  例外(Exceptions)


例外是將錯誤或例外事件傳給呼叫方程式的一種特殊手段。


對於錯誤原因不了解,可把控制權轉交給系統其他能夠解釋錯誤並採取對應的部分。


例外也可清理程式碼中的雜亂。


例外基本結構:子程式使用throw拋出一個例外物件,例外物件再被上層的try-catch捕捉。


表8-1整理出三種語言例外處理的差異


例外注意事項:


用例外通知程式的其他部分,發生了不可忽略的錯誤


這段中文翻得怪怪的?我認為是在說產品程式與除錯程式要分開,不要讓除錯程式影響到產品程式的部分。


只有在真正例外的情況下,才拋出例外


例外與斷言類似,都是處理罕見的或是永遠不該發生的情況。


例外可以處理預料之外的狀況,也會破壞封裝性增加程式複雜度,上層函式必須認識這些例外。


 


不能用例外來推卸責任


 


能夠局部處理就要局部處理,不要拋出例外。


 


避免在建構函式和解構函式中拋出例外,除非能在同一個地方捕捉


 


C++解構函數若是出現例外,會沒有完成解構,會出現記憶體洩漏的情況。


 


在恰當的抽象層次拋出例外


 


比較兩個範例可知有何不同,第一個範例丟出的例外是更底層的例外,第二個範例丟出同層的例外。


 


把例外傳給呼叫方時,確保例外的抽象層次與子程式介面的抽象層次是一致的。


 


在例外訊息中加入關於導致例外發生的全部資訊


 


例外發生的時候要上傳例外相關訊息,才能知道例外發生的原因。


 


避免使用空的catch述句


 


catch區塊不要留下空白,至少要寫logfile。


 


了解所用函式庫可能拋出的例外


 


要抓到函式庫會丟出來的例外,才不會導致程式崩潰。


 


考慮建立一個集中的例外報告機制


 


統一例外顯示訊息的格式。


 


把專案中對例外的使用標準化


 


可以用以下幾種途徑將例外的使用標準化:


 


C++允許拋出各式各樣的物件、資料及指標,應該建立一個標準。為了與其他語言相容,可考慮只拋出std::exception基底類別衍生出的物件。


考慮建立專案的特定例外類別,用做所有可能拋出的例外的基底類別,這樣就能把日誌、錯誤報告集中並標準化。


規定在何種場合允許使用throw-catch在局部對於錯誤進行處理。


規定在何種場合允許程式碼拋出不在局部進行處理的例外。


確定是否要採用集中的例外報告機制。


規定是否允許在建構函式與解構函式中使用例外。


 


考慮例外的替代方案


作者勉勵讀者多想想有沒有替代方案。


有人提出不要處理錯誤,可以釋放資源讓使用者重新輸入資料。


8.5  隔離程式,使之包容由錯誤造成的損害


隔離是一種容損策略。


隔離的一種方法是把某些介面選定為安全區域的邊界,對於穿越安全區域邊界的資料進行合法性的驗證,並且當資料非法的時候做出敏銳的反應。


在輸入資料時將其轉換為恰當的類型


隔欄與斷言的關係


隔欄外部的程式應用錯誤處理技術,隔欄內部的的程式應使用斷言技術。


8.6  輔助除錯的程式碼


不要自動地把產品版的限制強加在開發版之上


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


儘早引入輔助除錯的程式碼


採用進攻式程式設計


開發階段讓處理例外顯現出來,產品程式碼運行時能夠自我恢復。


進攻式程式設計的方法:


確保斷言會使程式終止運行,通常情況死掉的程式所造成損失比殘廢的程式少得多。問題越大,才能夠被修復。


掌握所有的記憶體,可讓你檢測到記憶體配置錯誤。


掌握所有檔案或資料流,可以排除檔案格式的錯誤。


確保case述句default或else分支都能產生嚴重的錯誤,讓這些錯誤不會被忽視。


在刪除一個物件之前,把它填滿垃圾資料。(?)


如果環境允許,可將程式自動寄logfile,可了解已發佈的軟體還有哪些錯誤。


 


計畫移除輔助除錯的程式碼


避免除錯程式碼與一般程式碼糾纏不清,有下列方法:


使用類似像ant和make版本控制工具和make工具


使用內建的前置處理器


編寫自己的前置處理器


使用除錯木樁


除錯木樁就是測試小程式。


8.7  決定在產品程式碼中該保留多少防禦性程式碼


判斷哪些防禦性程式可以留在產品程式?


保留那些檢查重要錯誤的程式碼


去掉檢查細微錯誤的程式碼


去除會導致程式硬性崩潰的程式碼


保留可讓程式優雅崩潰的程式碼


為技術支援人員記錄下錯誤訊息


確認在程式碼中的錯誤訊息式友善的


8.8  對防禦性程式設計採取防禦的姿態


要考慮好在什麼地方進行防禦,然後因地制宜調整防禦性程式設計的優先級別。


核對表:防禦性程式設計


一般事宜


 


子程式是否保護自己免遭有害輸入資料的破壞?


用斷言說明程式的假定,其中包括前置條件和後置條件


斷言是否只是用來說明從不應該發生的狀況?


架構或高層設計中規定一組特定的錯誤技術?


讓錯誤處理更傾向於健全性?或是正確性?


建立隔欄阻止錯誤造成的傷害?減少其他關注錯誤處理的程式碼數量?


是否使用輔助除錯程式?


啟用或禁用輔助程式是否容易?


防禦性程式設計程式碼數量是否適宜?


開發階段是否使用進攻式程式設計來使錯誤難以被忽視?


 


例外


 


定義一套標準化例外處理方案?


考慮過例外以外的其他替代方案?


盡可能局部處理錯誤,而不是丟出例外。


避免在建構與解構程式拋出例外


例外使否與拋出的例外在同一層抽象層次?


例外是否包含例外相關訊息


程式中是否有空的catch區塊?


 


安全適宜


 


檢查有害輸入資料?檢查故意緩衝區溢出、SQL注入、HTML注入、整數溢出及其他惡意輸入資料?


檢查所有錯誤回傳?


捕捉了所有的例外?


錯誤訊息避免透露太多有利攻擊者的訊息。


 


要點


 


最終產品程式碼對錯誤的處理方式應該要比「垃圾進,垃圾出」複雜許多。


防禦性程式設計讓錯誤更容易發現、更容易修改、減少錯誤對產品程式的破壞。


斷言可幫助人提早發現錯誤


如何處理錯誤輸入的決策是關鍵決策,也是高層設計決策。


例外提供與正常流程不同的錯誤處理手段。


產品開發程式的限制不適用於開發中的軟體,可在開發程式中添加排除錯誤的程式碼。


 


 


留言

這個網誌中的熱門文章

異世界NTR web版第三章 觀後感 喧賓奪主 ,反派實力過強

泛而不精的我被逐出了勇者隊伍 web第三章 觀後感 菲莉真能打; 露娜超爽der

持有縮小技能的D級冒險者,與聖女結婚並加入勇者團隊 漫畫 01-04 觀後感 大我與小我