软件构造 6-3 Assertions and Defensive Programming
6.3 斷言與防御式編程
一. 斷言 assert
??首先要遵循:以盡量不要引入 bug 的原則(防御式編程)編程。
- 靜態檢查
- 動態檢查
- 不可變類型
- 不可變值
- 不可變引用
??若無法避免,縮小 bug 的范圍:
- 限定在一個方法內部,不擴散
- 盡快失敗,就容易發現、越早修復
??斷言就是一種 fail fast,避免 bug 的擴散。
1. assertion
??斷言:在開發階段的代碼中嵌入,檢驗某些“假設”是否成立。若成立,表明程序運行正常,否則表明存在錯誤。
public class AssertionTest {public static void main(String[] args) {int number = -5; // assumed number is not negative// This assert also serve as documentationassert (number >= 0) : "number is negative: " + number;// do somethingSystem.out.println("The number is " + number);} }??每個斷言都包含一個您認為在程序執行時為真的布爾表達式。
- 若不為真將出現 AssertionError
- 出現 AssertionError ,意味著內部某些假設被違反了
- 增強程序員對代碼質量的信心:對代碼所做的假設都保持正確
??assert 具有以下兩種方式之一:
- assert condition
- assert condition : message:message 可以描述錯誤原因、記錄案發現場
??有時候為了安全性,防止錯誤對后續程序造成影響,release 版本使用 assert,但一般不使用。
2. assert 適用范圍
?? assert 可以使用在各種不變量中:
- Internal Invariants 內部不變量
- Rep Invariants 表示不變量:checkRep()
- Control Flow Invariants 控制流不變量
- Pre conditions of methods 方法的前置條件
- Post conditions of methods 方法的后置條件,如:
- Control-flow 控制流,如:不使用不分流;判斷是否執行到不該執行的代碼之后
??其實等價于使用語句如:
default: throw new AssertionError ("must be a vowel, but was: " + vowel);??assert 還可以判斷:
- 指針是否為空
- 傳入方法的數組或其他容器至少可以包含 X 個數據元素
- 一個表被初始化為實數值
- 當方法開始執行(或完成)時,容器是空的(或滿的)
- 從一個高度優化的、復雜的方法得到的結果與從一個較慢但清晰編寫的例程得到的結果相匹配
??實際上可以對程序執行時的某些條件進行驗證。
??實際上, assert
- 斷言主要用于開發階段,避免引入和幫助發現 bug
- 實際運行階段,不再使用斷言,運行階段對你編寫的程序不可以使用 assert 以保證健壯性。但可以使用異常
- 避免降低性能
- 使用斷言的主要目的是為了在開發階段調試程序、盡快避免錯誤
??因此,程序之外的事,不受你控制,不要亂斷言,注意:
- 如:文件/網絡/用戶輸入等不要檢測。這是因為檢測了也沒辦法
- 斷言只是檢查程序的內部狀態是否符合規約
- 斷言一旦 false ,程序就停止執行
- 你的代碼無法保證不出現此類外部錯誤 ,外部錯誤要使用 Exception 機制去處理。而 assert 一般檢測程序內部的錯誤(開發階段)。
??正常情況下,Java 缺省關閉斷言,要使用 assert 時要記得打開 (-ea)。
??默認情況下, assert 功能是關閉的,這是因為
- 斷言不應該出現在工程的 release 版本。client 想要挽救 bug 而不是中止程序。
- 斷言非常影響運行時的性能
??開啟 assert 需要使用相應的命令或者打開相應的按鈕
3. 斷言使用的指導
??assert 與 Exception 的區別
- 斷言的目標是保證正確性,使用在開發階段,針對程序員。使用斷言處理“絕不應該發生”的情況
- 錯誤/異常處理的目標是保證健壯性,使用在發布階段,針對用戶端。使用異常來處理你“預料到可以發生”的不正常情況。
??對于 pre-/post-condition ,是否使用 assert 有爭議(前者通常用 Exception, 后者通常用 assert)。爭議的理由有以下幾點
- 不管是否 -ea, spec 中的 pre-/post-conditions 都能夠被保證
- 即使 spec 被違反,也不應通過 assert 直接 fail ,而是應拋出具體的 RunTimeException 。
??我們使用如下原則:
??如果參數來自于外部(不受自己控制,如 public 方法的參數),使用異常處理;如果來自于自己所寫的其他代碼,可以使用斷言來幫助發現錯誤(例如 post condition 就需要;如 private 方法)。
??異常能做到斷言能做的事。斷言和異常處理都可以處理同樣的錯誤。但由于斷言可以容易地開啟關閉,故斷言的使用仍然廣泛。
??開發階段用斷言盡可能消除 bugs 在發行版本里用異常處理機制處理漏掉的錯誤。
二. 防御性編程及工具
1. 阻止程序的非法輸入
??正確性使得程序員只需對正確數據有正確反應,錯誤數據可以隨意處理。但是從健壯性角度,仍然需對錯誤數據做出良好的反應,如無反應、提示錯誤信息或不允許輸入錯誤數據。這時規約應該添加上對錯誤的處理方法。
??對來自外部的數據源要仔細檢查,例如:文件、網絡數據、用戶輸入等。
2. 設置路障
??不安全的方法(輸入可能不合法的方法)通過檢測手段檢測數據輸入是否有錯誤。路障可以使不正確的輸入被過濾掉,使得不安全的方法變成安全的方法。
??可以使用 public 和 private 方法判定,前者為不安全的方法,后者可能是安全的方法。
??類的 public 方法接收到的外部數據都應被認為是 dirty 的,需要處理干凈再傳遞到private 方法——隔離艙方法、操作間技術。
??“隔離艙”外部的函數應使用異常處理,“隔離艙”內的函數應使用斷言。
??這種方法是代理模式(Proxy 設計模式)。代理類可以做數據校驗。
三. SpotBugs 工具
??SpotBugs 是 Java 靜態代碼分析工具。
總結
以上是生活随笔為你收集整理的软件构造 6-3 Assertions and Defensive Programming的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Autofac 批量注入
- 下一篇: c语言rr算法,[判断题] 在RR、PF