天天看點

程式設計精粹--編寫高品質C語言代碼(3):自己設計并使用斷言(二)

接着上一遍文章,繼續學習如何自己設計并使用斷言,來更加容易,更加不費力地自動尋找出程式中的錯誤。

首先看一個簡單的壓縮還原程式:

以上這個程式不是原書中的程式,個人感覺原書中的程式有問題,是以進行了一些小修改。這個程式的一個關鍵點就是如果在輸入資料中找到了bReapeatCode,它就認為其後的兩個位元組分别代表重複的還原字元以及該字元的重複次數。按照上一篇文章講過的,為了提高程式的健壯性,可以利用斷言對函數參數的有效性進行檢查。除此之外,其實還有許多其他事情可以做,例如對緩沖區的資料進行确認。

仔細思考一下,上面一個程式進行一次譯碼,總共需要三個位元組。是以壓縮程式不應該對兩個連續的字元進行壓縮,當然對連續三個字元進行壓縮也沒有什麼好處。是以壓縮程式應該隻對連續三個以上的字元進行壓縮。還有一個情況,就是如果原始資料中含有bReatCode,就必須對其進行特殊處理,否則解壓程式會誤以為它是一個壓縮字元序列的開始。是以當原始資料中出現了bReatCode時,就把它再重複一次,以便和真正的壓縮字元序列差別。

是以我們可以使用斷言來對這兩個特性進行檢驗:

ASSERT(size>=4||(size==1&&b==Reaptcode));

如果斷言失敗說明pbFrom所指向的資料有問題或者壓縮程式有問題。是以利用斷言,我們不僅可以檢查文法上不可能發生的情況,而且可以利用斷言來檢驗程式邏輯上不可能發生的錯誤。

利用斷言來檢查不可能發生的情況。

讓我們接下來看字元解壓程式的另一個版本:

仔細觀察這兩個程式,雖然功能是一樣的,但是第一個版本利用了防錯性程式設計。我們可以分析一下,外層循環盡管不太可能出現pbEnd會大于pbFrom,但是一旦出現,程式的第一個版本會跳出外層循環,繼續運作,而第二個版本程式則可能崩潰。同樣對于内層循環,一旦出現size為0的情況,第一個版本的程式可以很好的退出循環,而第二個版本則無法做到。

似乎第一個版本更加合理,也更加聰明,但是如果出于某種原因pbFrom被加過了pbEnd,第一個版本的程式可以在程式造成過多的損害之前,它就會退出,而第二個版本的程式則會企圖對整個記憶體中的内容進行解壓,進而引起程式崩潰,使用者肯定會發現這個錯誤。是以實際情況就是這樣:防錯性程式設計雖然常常被譽為有較好的編碼風格,但是它卻隐瞞了錯誤。但是這并不意味者我們應該放棄防錯性程式設計。我們希望在進行防錯性程式設計時,錯誤不要被隐瞞。是以對于上面的程式,我們可以一方面一如既往地使用防錯性程式設計,另一方面在事情變槽的情況下利用斷言進行報警。

ASSERT(bpFrom==pbEnd)用來驗證函數的正常終止。由于采用了相應的防錯性程式設計,程式的傳遞版本可以保證出了毛病時使用者不受損失,而在程式的調試版本中,錯誤仍然可以被報告出來。是以在編碼之前都要問自己:“在進行防錯性程式設計時,程式中隐瞞了錯誤嗎?”如果答案是肯定的,就要在程式中加上斷言,以對這些錯誤進行報警。

在進行防錯性程式設計時,不要隐瞞錯誤。

同時,在編寫代碼時,要抓住一切機會對程式的結果進行驗證。要盡可能地使用不同的算法,而且要使其不僅僅是同一算法的又一實作。如果不同算法産生的結果不同,就會觸發斷言。通過使用不同的算法不僅可以發現算法實作中的錯誤,而且增加了發現算法本身錯誤的可能性。當然這并不意味着每個函數都得有兩個版本,正确的做法是隻對程式的關鍵部分這樣做。

          要利用不同的算法對程式的結果進行确認。

盡管利用不同算法的執行結果來對程式進行确認,可以幫助我們發現程式中的錯誤。但這畢竟要等到算法執行結束之後才能發現錯誤。有時候程式員應該在程式中進行初始檢查,這樣可以盡快發現錯誤,否則錯誤會隐藏一段時間。

         不要等待錯誤發生,要使用初始檢查程式。

總結: 

1,防錯性程式設計會隐瞞錯誤。當進行防錯性編碼時如果“不可能發生”的情況确實發生了,要使用斷言報警。

2,利用不同的算法對程式結果進行确認,當同一問題的不同算法出現不同結果時,觸發斷言。

3,使用初始檢查程式,盡早發現程式中的錯誤。

最後以作者的一句話結束這篇文章:

         測試者的工作并不隻是針對你的程式進行測試,查出自己程式中的錯誤畢竟是你自己的工作。