最近在看【美】Pete GoodLiffe寫的《編程匠藝》,很有感觸。邊讀邊想,發現能夠與作者產生強烈的共鳴,這真是一種美妙的感覺。所以想把與作者產生共鳴和值得學習的一些地方記下來,來加深對這些被實踐證明了是正確的編程技巧的掌握。
防禦性編程:顧名思義,防禦性編程是一種細緻的,謹慎的編程方法。為了開發可靠的軟體產品,我們要謹慎地設計系統的每個細節,便是其能儘可能的
“保護”自己,,我們通過明確的代碼中增加很多的假設,當假設在現實的客戶環境中執行時才不至於崩潰,給一些客戶莫名奇妙的錯誤或者異常。防禦性編程是一種防衛方式,而不是一種補救方式。
圖1 修補性的編程
下面是常見的防禦性編程技巧:
1.
使用好的編碼風格和合理的設計:
好的編碼風格會讓人耳目一新,而且越是清晰地代碼,越是體現了作者思路的清晰度,模組組件內部的高內聚,低耦合也會使代碼的維護和使用更加便捷,更不容易出錯。
2.
不要倉促的編寫代碼:
不要在腦袋裡有了大體思路之後就馬上很“職業”地劈裡啪啦的敲下代碼,然後大體檢查後剩下的就交給編譯器去檢查吧,於是運行通過,編寫下面一個函數,這樣就會隱藏大量的危險的代碼,久而久之就會陷入自己給自己挖的大坑裡,拔都拔不出腿。
關鍵: 欲速則不達。每敲一個字,都要想清楚自己要輸入的是什麼。
3.
不相信任何人:
不相信任何可能給自己寫的代碼帶來麻煩的人,這些人包括:真正的客戶,惡意的用(有可能是駭客),用戶端的代碼(Client Code),運行環境(可能存在磁碟空間已滿,網路斷開等),外部運行庫(自己寫的代碼所依賴的外部dll已從請求位置移除)。
4 .
編碼的目標是清晰,不是一味地准求簡潔:
如果從簡潔(可能理解起來比較困惑)和清晰(可能比較冗長)的代碼裡選擇一種,我想大部分人會選擇清晰地代碼,儘管比較冗長(畢竟代碼不是寫在花銀子買來的紙上的 )。
5.
不想讓其他人做他們不該做的事情:
設計上不想讓其他人訪問的屬性或者方法要盡量將可訪問性限制在類或者包的內部。
6.
編譯時間開啟所有的警告開關
日益發達,功能強大的編譯器會告訴我們,哪些代碼寫的不太符合規範,哪些變數聲明了未使用等等這些可能在實際的運行環境中給我們帶來意外結果,我們卻感覺莫名其妙,無從下手修補的代碼。重視編譯後的警告資訊會讓我們的代碼更加健壯。
7.
使用安全的資料結構:
最常見安全隱患是緩衝區溢位,例如:一個buffer的長度是10,但是向buffer寫入了長度為11的資料,這樣會可能覆蓋掉其他的資料,這樣可能會造成遭難性的影響。當然現在.net 這樣優秀的開發平台有CLR去管理記憶體,給我們騰出了更多的時間和精力去考慮我們想要用程式碼完成的事情。
8.
檢查所有的傳回值:
如果一個函數有傳回值,這樣做肯定是有理由的。要對這個傳回值進行檢查,如果不對傳回值檢查就會產生很多難以察覺的錯誤。檢查這個傳回值,這個傳回值可能是一個錯誤碼(C程式一般都是返回錯誤碼吧),必須辨別這個代碼並處理所有的錯誤,忍受這個錯誤,危險就會悄無聲息的潛入我們的程式。C#提供了沿堆棧逐級向外拋出的異常機制,我們應該在寫的C#代碼中,對不同層級的異常做合理的封裝(底層異常要體現底層這個層級來例如:DBException,業務層的異常要體現出業務類的異常來ProductInfoInitialException)。
9.審慎的處理記憶體等寶貴的資源:
這些資源套件括:記憶體,磁碟檔案,網路連接,資料庫連接。哈哈,現在jvm,clr都可以做到自動的清理資源和記憶體回收,我們真是太幸福了,但是也不要太大意了,我們還是需要顯示的釋放那些我們不再需要的資源,畢竟jvm和clr都是遵循一定的機制(詳見http://www.cnblogs.com/anorthwolf/archive/2009/12/07/1618744.html),我們必須顯示的終止對那些不再使用和不會被自動清理的對象的引用,不太先進的記憶體回收行程會被循環參考對象所蒙蔽。
10.盡量晚的聲明變數:
也就是要靠著使用變數最近的程式碼片段來聲明變數。
11.在聲明變數的位置初始設定變數:
如果初始化了所有的變數,那麼他的用途就是明確的。“如果我不初始化它,我就不關心它”的經驗主義是錯誤的,如果一個變數在一個位置聲明,在另一個位置初始化,並在第三個地方被使用,那麼一旦初始化代碼被跳過,就會得到意想不到的結果,到時候我們再去尋找原因,那就難嘍。
12.小心的進行強制類型裝換:
有些類型之間雖然可以進行強制類型轉換,但是有些類型在轉換的過程中會丟失掉一些資料。
13.提供預設的行為:
例如:Switch語句,要對default 有預設的實現。
14.檢查數值的上下線:
.net 架構中已經對一些數實值型別有了範圍的約束,但是如果我們在一些業務開發中要對是否符合現實情況作出檢查:例如年齡不能為負數等等。
15.約束:
約束主要包括:
1.5.1 前置條件:輸入一段代碼之前必須為真。
1.5.2 後置條件:輸入一段代碼之後必須為真。
1.5.3 不變條件:程式運行到一個特定點,必須為真。
1.5.3 斷言:任何其他關於程式在給定位置的狀態的陳述。
具體內容是:
檢查所有的數組邊界。
在廢除指標前,斷言指標是否清零。
確保函數參數的有效性。
在函數結果返回前,進行充分的檢查。
在操作對象之前證明他的狀態時一致的。
總結一下,編寫正確而且優秀的代碼是很重要的,這需要記錄下你所做的所有的設想。這樣會使程式維護起來更加的容易,會使錯誤減少。防禦性編程就是一種預想最壞的情況並為之做好準備的方法。他是一種可以防止簡單的錯誤變成難以找到的錯誤的技術。與防禦性代碼一起使用編入代碼的約束,會使程式更加的健壯。