程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> 編程精粹--編寫高質量C語言代碼(3):自己設計並使用斷言(二)

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

編輯:關於C語言

接著上一遍文章<<編程精粹--編寫高質量C語言代碼(2):自己設計並使用斷言(一)>>,繼續學習如何自己設計並使用斷言,來更加容易,更加不費力地自動尋找出程序中的錯誤。

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

byte* pbExpand(byte *pbFrom,byte *pbTo,size_t sizeFrom)
{
   byte b, *bpEnd;
   size_t size;
   pbEnd=pbFrom+sizeFrom;
   while(pbFrom0)
		    *pbTo++=b;	
      }
      else
  	  {
          *pbTo++=b;	
      }
      /** 原文中以下代碼沒有被注釋,個人感覺有問題 */
	  // return pbTo; 
   }
   /** 原文中沒有這行代碼 */
   return pbTo;	
}

以上這個程序不是原書中的程序,個人感覺原書中的程序有問題,所以進行了一些小修改。這個程序的一個關鍵點就是如果在輸入數據中找到了bReapeatCode,它就認為其後的兩個字節分別代表重復的還原字符以及該字符的重復次數。按照上一篇文章<<編程精粹--編寫高質量C語言代碼(2):自己設計並使用斷言(一)>>講過的,為了提高程序的健壯性,可以利用斷言對函數參數的有效性進行檢查。除此之外,其實還有許多其他事情可以做,例如對緩沖區的數據進行確認。

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

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

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

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

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

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

byte* pbExpand(byte *pbFrom,byte *pbTo,size_t sizeFrom)
{
   byte b, *bpEnd;
   size_t size;
   pbEnd=pbFrom+sizeFrom;
   while(pbFrom!=pbEnd)
   {
   	  b=*pbFrom++
   	  if(b==bRepeatCode)
   	  {
  	     /**在pbTo開始的位置存儲"size"個b */
		 b=*pbFrom++;
		 size=(size_t)*pbFrom++;
		 /** 檢查原始數據的有效性 */ 
		 ASSERT(size>3||(size==1&&b==bReaptCode));
		 do
		    *pbTo++=b;
         /** 原文是 while(size--!=0),
		     個人感覺有問題,因為會多循環一次 
			  於是改成如下語句
	     */ 
		 while(--size!=0);	
      }
      else
  	  {
          *pbTo++=b;	
      }
      /** 原文中以下代碼沒有被注釋,個人感覺有問題 */
	  // return pbTo; 
   }
   /** 原文中沒有這行代碼 */
   return pbTo;	
}

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

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

byte* pbExpand(byte *pbFrom,byte *pbTo,size_t sizeFrom)
{
   byte b, *bpEnd;
   size_t size;
   pbEnd=pbFrom+sizeFrom;
   while(pbFrom0)
		    *pbTo++=b;	
      }
      else
  	  {
          *pbTo++=b;	
      }
      /** 原文中以下代碼沒有被注釋,個人感覺有問題 */
	  // return pbTo; 
   }
   ASSERT(pbFrom==pbEnd);
   /** 原文中沒有這行代碼 */
   return pbTo;	
}

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

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

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

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

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

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

總結:

1,防錯性程序設計會隱瞞錯誤。當進行防錯性編碼時如果“不可能發生”的情況確實發生了,要使用斷言報警。
2,利用不同的算法對程序結果進行確認,當同一問題的不同算法出現不同結果時,觸發斷言。
3,使用初始檢查程序,盡早發現程序中的錯誤。

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

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


  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved