軟件架構————防禦式編程

防禦式編程的主要思想:子程序應該不因傳入錯誤數據而被破壞,哪怕是由其他子程序產生的錯誤數據。更一般地說,其核心想法是要承認程序都會有問題,都需要被修改。

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

1.檢查所有源於外部的數據的值

當從文件、用戶、網絡或其他外部接口中獲取數據時,應檢查所獲得的數據值,以確保它在允許的範圍內。對於數值,要確保它在可接受的取值範圍內;對於字符串,要確保其不過長。如果字符串代表的是某個特定範圍內的數據,那麼要確認其取值合乎用途,否則就應該拒絕接受。如果在開發過程中需要有安全的應用程序,還要格外注意那些狡猾的可能是攻擊你的系統的數據,包括企圖令緩衝區溢出的數據、注入的SQL命令、整數溢出以及傳遞給系統調用的數據,等等。

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

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

3.決定如何處理錯誤的輸入數據見下面的講解


斷言:

斷言是指在開發期間使用的、讓程序在運行時進行自檢的代碼(通常是一個子程序或宏),它對於大型的複雜程序或可靠性要求極高的程序來說尤爲有用。通過使用斷言,可以快速排查出因修改代碼或者別的原因,而弄進程序裏的不匹配的接口假設和錯誤等。

正常情況下,並不希望用戶看到產品代碼中的斷言信息;斷言主要是用於開發和維護階段。通常,斷言只是在開發階段被編譯到目標代碼中,而在生成產品代碼時並不編譯進去。在開發階段,斷言可以幫助查清相互矛盾的假定、預料之外的情況以及傳給子程序的錯誤數據等。

使用斷言的建議:

1.用錯誤處理代碼來處理預期會發生的狀況,用斷言來處理絕不應該發生的狀況

2.避免把需要執行的代碼放入斷言中:如果把代碼寫在斷言裏,那麼當關閉斷言功能時,編譯器很可能就把這些代碼排除在外不執行了。最好將函數的返回值傳遞到一個臨時變量中,然後讓斷言判斷這個臨時變量。

3.用斷言來註解並驗證前條件和後條件:

前條件是子程序或類的調用方代碼在調用子程序或實例化對象之前要確保爲真的屬性。

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

4.對於高健壯性的代碼,應該先使用斷言再處理錯誤


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


錯誤處理技術:

斷言可以處理代碼中不應該發生的錯誤,那麼該如何處理那些預料中可能要發生的錯誤呢?

1.返回中立值:返回一個對程序沒有危害的數據。(數值可以返回0,指針可以返回空

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

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

4.換用最接近的合法值(如某個數超過了某個界限,則返回界限的邊界值

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

6.返回一個錯誤碼

7.調用錯誤處理子程序或對象:優點在於可以把錯誤處理的職責集中,從而讓調試工作更加簡單,但是代價是整個程序都要知道這個集中點並與之緊密耦合。

8.當錯誤發生時顯示出錯誤消息:這種方法可以錯誤處理的開銷減到最小,但是也有可能讓用戶界面中出現的信息散佈到整個應用程序中。而且攻擊者可能會利用錯誤信息來發現如何攻擊這個系統。

9.用最妥當的方式在局部處理錯誤:這種方法給每個程序員很大的靈活度,但也帶來了顯著的風險,即系統的整體性能將無法滿足對其正確性或可靠性的需求。

10.關閉程序:有些系統需要一旦檢測到錯誤發生就會關閉。


健壯性與正確性:

正確性:意味着永不返回不準確的結果,哪怕不返回結果也比返回不準確的結果好。

健壯性:意味着要不斷嘗試採取某些措施,以保證軟件可以持續地運轉下去,哪怕有時做出一些不夠準確的結果。


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

應該在整個程序裏採用一致的方式處理非法的參數。確定一種通用的處理錯誤參數的方法,是架構層次的設計決策,需要在那裏的某個層次上解決。

除非已確立了一套不對系統調用進行錯誤檢查的架構性知道建議,否則請在每個系統調用後檢查錯誤碼。一旦檢測到錯誤,就記下錯誤代號和它的描述信息。


異常:

異常就是把代碼中的錯誤或異常事件傳遞給調用方代碼的一種特殊手段。如果在一個子程序中遇到了預料之外的情況,但不知道該如何處理的話,這就可以拋出一個異常。對出錯的前因後果不甚瞭解的代碼,可以把對控制權裝交給系統中其他能更好解釋錯誤並採取措施的部分。

建議:

1.用異常通知程序的其他部分,發生了不可忽略的錯誤:相比錯誤處理機制有可能會導致錯誤在不知不覺中向外擴散,而異常則消除了這種可能。

2.只在真正例外的情況下才拋出異常:異常需要做出一個取捨,一方面它是一種強大的用來處理預料之外情況的途徑,另一方面程序的複雜度會因此增加。由於調用子程序的代碼需要了解被調用代碼中可能會拋出異常,因此異常弱化了封裝性。

3.不能用異常來推卸責任:如果某種的錯誤情況可以在局部處理,那就應該在局部處理掉它。不要把本來可以在局部處理的錯誤當成一個未被捕獲的異常拋出去。

4.避免在構造函數和析構函數中拋出異常,除非在同一地方把他們捕獲:這會讓異常規則馬上就變得非常複雜。

5.在恰當抽象層次拋出異常:子程序應在其接口中展現出一隻的抽象,類也是如此。拋出的異常也是程序接口的一部分,和其他具體的數據類型一樣。

6.在異常消息中加入關於導致異常發生的全部信息:確保信息中的內容容易讓人理解異常的原因。

7.避免使用空的catch語句:因爲要是做了就要麼就是try裏的代碼不對,因爲它無故拋出了一個異常;要麼就是catch中的代碼不對,因爲它沒能處理一個有效的異常。

8.瞭解所用函數庫可能拋出的異常

9.考慮創建一個集中的異常報告機制

10.把項目中對異常的使用標準化

11.考慮異常的替換方案:應該自始至終考慮各種各樣的錯誤處理機制:在局部處理錯誤、使用錯誤碼來傳遞錯誤、在日誌文件中記錄調試信息、關閉系統或其他的一些方式等。僅僅因爲編程語言提供了異常處理機制而使用異常,是典型的”爲用而用“;這也是典型的”在一種語言上編程“而非”深入一種語言去編程“的例子。


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

以防禦式編程爲目的而進行隔離的一種方法,是把某些接口選定爲”安全“區域的邊界。對穿越安全區域邊界的數據進行合法性校驗,並當數據非法時做出敏銳的反應。在類的層次上也可以做同樣的處理方式。

在輸入數據時將其轉換爲恰當的類型:因爲程序在長時間傳遞類型不明的數據會增加程序的複雜度和崩潰的可能性。因此,應該在輸入數據後立即將其轉換到恰當的類型。


確定在產品中該保留多少防禦式代碼:

1.保留那些檢查重錯誤的代碼

2.去掉檢查細微錯誤的代碼

3.去掉可以導致程序硬性崩潰的代碼

4.保留可以讓程序穩妥地崩潰的代碼

5.爲你的技術支持人員記錄錯誤信息

6.確認留在代碼中的錯誤消息是友好的

發佈了86 篇原創文章 · 獲贊 10 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章