http://kb.cnblogs.com/page/140426/
2012
英文鏈接:Defensive Programming: Being Just-Enough Paranoid
每當(dāng)程序員突然遇到某個(gè)bug并不知道怎么改的時(shí)候,他們會(huì)添加一些“防御性代碼”來使編碼更安全并且更容易找到問題的原因。有時(shí)這樣做可以消除錯(cuò)誤。他們加強(qiáng)了數(shù)據(jù)的有效性驗(yàn)證——檢驗(yàn)輸入框、輸出框和返回值的內(nèi)容;審查并改善錯(cuò)誤處理——可能會(huì)添加一些針對于出現(xiàn)“異?!鼻闆r的驗(yàn)證代碼;添加一些有用的日志和診斷。換句話說,就是最好一開始就應(yīng)該出現(xiàn)在那兒的代碼。
事先預(yù)料到無法預(yù)料的情況
防御性編程的關(guān)鍵在于未雨綢繆,防患于未然。
—— Steve McConnell, 《Code Complete》(中文版:《代碼大全》)
在Steve McConnell的經(jīng)典編程書籍,《Code Complete》里的幾個(gè)簡單章節(jié)里講到了防御性編程的幾條基本規(guī)則:
1. 保護(hù)你的代碼不要受“外界”的無效數(shù)據(jù)影響,“外界”影響有很多種情況。外部系統(tǒng)的數(shù)據(jù),某個(gè)用戶的操作或模型/組件外面的數(shù)據(jù)。任何在控制范圍之外的東西都是危險(xiǎn)的,而任何在控制范圍之內(nèi)的都是安全的,所以要設(shè)立“安全區(qū)”。在安全區(qū)域的代碼會(huì)驗(yàn)證所有的輸入數(shù)據(jù):檢查所有輸入?yún)?shù)的類型,長度和取值范圍??梢酝ㄟ^雙擊來檢測有沒有溢出。
2. 當(dāng)檢驗(yàn)到了錯(cuò)誤的數(shù)據(jù)后,可以考慮如何處理它。防御性編程并不意味著要忍受錯(cuò)誤或是避開錯(cuò)誤。它意味著要從健壯性(如果遇到你能處理的問題時(shí)能保持程序運(yùn)行)和正確性(不會(huì)返回錯(cuò)誤的結(jié)果)之間權(quán)衡最合適的處理方式??梢赃x擇一種策略來處理錯(cuò)誤的數(shù)據(jù):報(bào)錯(cuò)并立刻停止程序(快速結(jié)束),返回一個(gè)替代的數(shù)據(jù)值,等等,總之要確保這個(gè)策略是明顯一致的。
3. 不要以為在代碼外進(jìn)行函數(shù)調(diào)用和方法調(diào)用會(huì)像你所想的那般順利。你要明白這一點(diǎn),并在外部的API和庫里測試你的錯(cuò)誤處理。
4. 在開發(fā)和測試的情況下,可以使用斷言來假設(shè)某種“可能出現(xiàn)”的條件并特別顯示出來。這對于需要不同的人在各個(gè)時(shí)間進(jìn)行維護(hù)的高可靠性大型系統(tǒng)來說尤其重要。
5. 添加診斷代碼可以智能記錄并追蹤代碼,它可以解釋運(yùn)行時(shí)當(dāng)前的情況,尤其在遇到某個(gè)問題時(shí)它的幫助會(huì)更大。
6. 錯(cuò)誤處理需要標(biāo)準(zhǔn)化。要考慮遇到“一般錯(cuò)誤”、“預(yù)料中錯(cuò)誤”和警告時(shí)的各種處理方式,決定好之后就不要再改了。
7. 只有在你需要的時(shí)候,并且你對編程語言的異常處理極為熟悉才可以使用異常處理。
在一般的錯(cuò)誤處理中使用異常處理的程序會(huì)引起可讀性和可維護(hù)性的代碼問題。
——《The Pragmatic Programmer》(中文版:《程序員修煉之道:從小工到專家》)
我想再加兩條規(guī)則。一個(gè)是Michael Nygard的Release It!中提到的,絕對不要去不斷地等待某個(gè)外部的調(diào)用,尤其是遠(yuǎn)程調(diào)用。如果什么地方出現(xiàn)問題了,時(shí)間會(huì)非常漫長。使用暫停/重試的邏輯方法和他的Circuit Breaker穩(wěn)定方案可以解決遠(yuǎn)程問題。
另一個(gè)規(guī)則是,對于像C和C++的語言,防御性編程也包括使用安全函數(shù)調(diào)用來避免緩沖區(qū)溢出和其他常見的代碼錯(cuò)誤。
不同類型的質(zhì)疑
The Pragmatic Programmer把防御性編程描述為“防御性質(zhì)疑”。它保護(hù)你的代碼不受其他人的錯(cuò)誤或你自己的錯(cuò)誤影響。如果懷疑數(shù)據(jù)的有效性,可以檢驗(yàn)數(shù)據(jù)的一致性和完整性。你不能測試所有的錯(cuò)誤,所以要使用斷言和異常處理來對付“發(fā)生了預(yù)料之外”的事情。在程序的測試中你會(huì)學(xué)到一些知識(shí),如果程序出錯(cuò)了,去找找還有什么地方會(huì)出錯(cuò)。著重注意最核心的重要代碼。
合理的質(zhì)疑編程是一種正確的編程習(xí)慣。不過質(zhì)疑太過分了可能過猶不及。在Clean Code(中文版:《代碼整潔之道》)里關(guān)于錯(cuò)誤處理的章節(jié)里,Michael Feathers提醒道:
(error handling)錯(cuò)誤處理的代碼可能會(huì)制約許多代碼的本質(zhì)意義
許多錯(cuò)誤處理代碼不僅會(huì)混淆代碼的主要流程(也就是代碼實(shí)際要做什么),還會(huì)混淆錯(cuò)誤處理本身的邏輯——這樣很難做到正確,很難審查和測試,很難在更改代碼后不引起錯(cuò)誤。代碼不再靈活安全了,它實(shí)際上會(huì)變得更脆弱,容易引發(fā)問題。
防御性編程可以采取的,有合理的質(zhì)疑方法,也有過分的質(zhì)疑方法,還有近于瘋狂的質(zhì)疑方法。
我第一個(gè)接觸的世界級系統(tǒng)是一個(gè)在跨越了美國加拿大的服務(wù)器上“Store and Forward”網(wǎng)路控制系統(tǒng)(也被稱為微型電腦)。它在網(wǎng)路上的分布式系統(tǒng),計(jì)劃作業(yè),和坐標(biāo)報(bào)告之間分享數(shù)據(jù)。它被設(shè)計(jì)為遇到網(wǎng)路問題時(shí)能靈活處理,在遇到操作失誤時(shí)會(huì)自動(dòng)恢復(fù)和重啟。這在當(dāng)時(shí)是非常具有技術(shù)性挑戰(zhàn)的。
最初維護(hù)這個(gè)系統(tǒng)的程序員并不相信網(wǎng)路,系統(tǒng)和操作會(huì)是永遠(yuǎn)正常的,也不相信其他人的代碼,甚至是自己的代碼是毫無破綻的。他是一個(gè)從化學(xué)專業(yè)自學(xué)轉(zhuǎn)到系統(tǒng)工程師的,他喜歡在晚上很晚的時(shí)候喝很多酒,并在那種狀態(tài)下寫幾千行松散的FORTRAN或匯編的代碼。代碼里充滿了錯(cuò)誤檢查、自我診斷和錯(cuò)誤校正,文件和數(shù)據(jù)包都有它們自己的校驗(yàn)和、文件級密碼和隱藏的控制標(biāo)簽,也有很多代碼可以控制計(jì)算異常和計(jì)時(shí)問題——代碼幾乎所有時(shí)間都在運(yùn)行。如果代碼遇到什么無法分析的問題,程序會(huì)崩潰并報(bào)告一個(gè)“退出標(biāo)簽”并且轉(zhuǎn)儲(chǔ)變量的內(nèi)容——就像現(xiàn)在所說的堆棧跟蹤。你理論上可以利用這些信息來檢查代碼并查出里面到底發(fā)生了什么。這些看起來都不是在學(xué)校里可以學(xué)到的。閱讀和運(yùn)行代碼不會(huì)再覺得受限制。
如果遇到難以修改的bug,使代碼不能繼續(xù)運(yùn)行。他會(huì)找一個(gè)辦法來處理bug使系統(tǒng)可以保持運(yùn)行。在他離開公司之后,如果代碼遇到某個(gè)網(wǎng)路上的bug,我就可以通過那些“錯(cuò)誤校驗(yàn)”代碼找到并修改這個(gè)bug。當(dāng)我解決完問題之后,就可以安全地移除這些“保護(hù)代碼”,這樣清理錯(cuò)誤處理代碼可以使我在維護(hù)系統(tǒng)時(shí)不會(huì)刪掉重要的東西。我設(shè)立了安全區(qū)——其實(shí)我也不知道怎么稱呼比較好——來分析什么數(shù)據(jù)是有效的,而什么是無效的。做到這點(diǎn)就能簡化防御性代碼以便我更改代碼也不會(huì)引起系統(tǒng)本身的出錯(cuò),并能保護(hù)核心代碼不受無效數(shù)據(jù)、代碼中某些錯(cuò)誤或操作失誤的影響。
維護(hù)代碼安全很簡單
防御性編碼的要點(diǎn)是讓代碼更安全并減少維護(hù)代碼的人的工作。防御性代碼和普通的代碼一樣都有bug,因?yàn)榉烙源a是用來處理異常的,所以測試尤其困難,也很難保證在代碼運(yùn)行時(shí)能正常工作。理解哪些條件下要使用防御性代碼并使用多少防御性編碼,需要在實(shí)際編程工作中多觀察來積累經(jīng)驗(yàn)。
許多涉及到設(shè)計(jì)和建立安全靈活系統(tǒng)的工作都是技術(shù)難以實(shí)現(xiàn)的或是花費(fèi)消耗極大的。而防御性編程兩者都沒有——它有些像防御性駕駛,也就是所有人都很容易去理解的。它需要規(guī)范和意識(shí),對細(xì)節(jié)加以注意,若我們想讓代碼變得安全,就都會(huì)用到它的。
聯(lián)系客服