多疑到剛剛好:防禦性程式設計

周慶成發表於2012-03-30

譯者吐槽:程式設計師都是好男人,因為他們一整天都在捫心自問:我到底錯在哪了,讓我知道,我一定改...

每當程式設計師突然遇到某個bug並不知道怎麼改的時候,他們會新增一些“防禦性程式碼”來使編碼更安全並且更容易找到問題的原因。有時這樣做可以消除錯誤。他們加強了資料的有效性驗證——檢驗輸入框、輸出框和返回值的內容;審查並改善錯誤處理——可能會新增一些針對於出現“異常”情況的驗證程式碼;新增一些有用的日誌和診斷。換句話說,就是最好一開始就應該出現在那兒的程式碼。

事先預料到無法預料的情況

防禦性程式設計的關鍵在於未雨綢繆,防患於未然。 ——Steve McConnell, 《Code Complete》(中文版:程式碼大全)

在Steve McConnell的經典程式設計書籍,《Code Complete》裡的幾個簡單章節裡講到了防禦性程式設計的幾條基本規則:

1.保護你的程式碼不要受“外界”的無效資料影響,“外界”影響有很多種情況。外部系統的資料,某個使用者的操作或模型/元件外面的資料。任何在控制範圍之外的東西都是危險的,而任何在控制範圍之內的都是安全的,所以要設立“安全區”。在安全區域的程式碼會驗證所有的輸入資料:檢查所有輸入引數的型別,長度和取值範圍。可以通過雙擊來檢測有沒有溢位。
2.當檢驗到了錯誤的資料後,可以考慮如何處理它。防禦性程式設計並不意味著要忍受錯誤或是避開錯誤。它意味著要從健壯性(如果遇到你能處理的問題時能保持程式執行)和正確性(不會返回錯誤的結果)之間權衡最合適的處理方式。可以選擇一種策略來處理錯誤的資料:報錯並立刻停止程式(快速結束),返回一個替代的資料值,等等,總之要確保這個策略是明顯一致的。
3.不要以為在程式碼外進行函式呼叫和方法呼叫會像你所想得那般順利。你要明白這一點,並在外部的API和庫裡測試你的錯誤處理。
4.在開發和測試的情況下,可以使用斷言來假設某種“可能出現”的條件並特別顯示出來。這對於需要不同的人在各個時間進行維護的高可靠性大型系統來說尤其重要。
5.新增診斷程式碼可以智慧記錄並追蹤程式碼,它可以解釋執行時當前的情況,尤其在遇到某個問題時它的幫助會更大。
6.錯誤處理需要標準化。要考慮遇到“一般錯誤”、“預料中錯誤”和警告時的各種處理方式,決定好之後就不要再改了。
7.只有在你需要的時候,並且你對程式語言的異常處理極為熟悉才可以使用異常處理。

在一般的錯誤處理中使用異常處理的程式會引起可讀性和可維護性的程式碼問題
——《The Pragmatic Programmer》(中文版:程式設計師修煉之道:從小工到專家)

我想再加兩條規則。一個是Michael Nygard的Release It!中提到的,絕對不要去不斷地等待某個外部的呼叫,尤其是遠端呼叫。如果什麼地方出現問題了,時間會非常漫長。使用暫停/重試的邏輯方法和他的Circuit Breaker穩定方案可以解決遠端問題。

另一個規則是,對於像C和C++的語言,防禦性程式設計也包括使用安全函式呼叫來避免緩衝區溢位和其他常見的程式碼錯誤。

不同型別的質疑

The Pragmatic Programmer把防禦性程式設計描述為“防禦性質疑”。它保護你的程式碼不受其他人的錯誤或你自己的錯誤影響。如果懷疑資料的有效性,可以檢驗資料的一致性和完整性。你不能測試所有的錯誤,所以要使用斷言和異常處理來對付“發生了預料之外”的事情。在程式的測試中你會學到一些知識,如果程式出錯了,去找找還有什麼地方會出錯。著重注意最核心的重要程式碼。

合理的質疑程式設計是一種正確的程式設計習慣。不過質疑太過分了可能過猶不及。在Clean Code(中文版:程式碼整潔之道)裡關於錯誤處理的章節裡,Michael Feathers提醒道:

(error handling)錯誤處理的程式碼可能會制約許多程式碼的本質意義

許多錯誤處理程式碼不僅會混淆程式碼的主要流程(也就是程式碼實際要做什麼),還會混淆錯誤處理本身的邏輯——這樣很難做到正確,很難審查和測試,很難在更改程式碼後不引起錯誤。程式碼不再靈活安全了,它實際上會變得更脆弱,容易引發問題。

防禦性程式設計可以採取的,有合理的質疑方法,也有過分的質疑方法,還有近於瘋狂的質疑方法。

我第一個接觸的世界級系統是一個在跨越了美國加拿大的伺服器上“Store and Forward”網路控制系統(也被稱為微型電腦)。它在網路上的分散式系統,計劃作業,和座標報告之間分享資料。它被設計為遇到網路問題時能靈活處理,在遇到操作失誤時會自動恢復和重啟。這在當時是非常具有技術性挑戰的。

最初維護這個系統的程式設計師並不相信網路,系統和操作會是永遠正常的,也不相信其他人的程式碼,甚至是自己的程式碼是毫無破綻的。他是一個從化學專業自學轉到系統工程師的,他喜歡在晚上很晚的時候喝很多酒,並在那種狀態下寫幾千行鬆散的FORTRAN或彙編的程式碼。程式碼裡充滿了錯誤檢查、自我診斷和錯誤校正,檔案和資料包都有它們自己的校驗和、檔案級密碼和隱藏的控制標籤,也有很多程式碼可以控制計算異常和計時問題——程式碼幾乎所有時間都在執行。如果程式碼遇到什麼無法分析的問題,程式會崩潰並報告一個“退出標籤”並且轉儲變數的內容——就像現在所說的堆疊跟蹤。你理論上可以利用這些資訊來檢查程式碼並查出裡面到底發生了什麼。這些看起來都不是在學校裡可以學到的。閱讀和執行程式碼不會再覺得受限制。

如果遇到難以修改的bug,使程式碼不能繼續執行。他會找一個辦法來處理bug使系統可以保持執行。在他離開公司之後,如果程式碼遇到某個網路上的bug,我就可以通過那些“錯誤校驗”程式碼找到並修改這個bug。當我解決完問題之後,就可以安全地移除這些“保護程式碼”,這樣清理錯誤處理程式碼可以使我在維護系統時不會刪掉重要的東西。我設立了安全區——其實我也不知道怎麼稱呼比較好——來分析什麼資料是有效的,而什麼是無效的。做到這點就能簡化防禦性程式碼以便我更改程式碼也不會引起系統本身的出錯,並能保護核心程式碼不受無效資料、程式碼中某些錯誤或操作失誤的影響。

維護程式碼安全很簡單

防禦性編碼的要點是讓程式碼更安全並減少維護程式碼的人的工作。防禦性程式碼和普通的程式碼一樣都有bug,因為防禦性程式碼是用來處理異常的,所以測試尤其困難,也很難保證在程式碼執行時能正常工作。理解哪些條件下要使用防禦性程式碼並使用多少防禦性編碼,需要在實際程式設計工作中多觀察來積累經驗。

許多涉及到設計和建立安全靈活系統的工作都是技術難以實現的或是花費消耗極大的。而防禦性程式設計兩者都沒有——它有些像防禦性駕駛,也就是所有人都很容易去理解的。它需要規範和意識,對細節加以注意,若我們想讓程式碼變得安全,就都會用到它的。

原文來自:http://swreflections.blogspot.com/2012/03/defensive-programming-being-just-enough.html(翻不了牆的就算了。這是我找到的一個可以訪問的轉載連結,不過也不穩定http://www.javacodegeeks.com/2012/03/defensive-programming-being-just-enough.html)

iTran樂譯

iTran樂譯參加活動,讀好文章!

相關文章