編寫優質無錯程式碼(2) (轉)

gugu99發表於2008-01-25
編寫優質無錯程式碼(2) (轉)[@more@]討論過程中,有人認為assert檢查的是, 而異常是可以恢復的意外情況。
所以,觀點3的支持者說:可恢復的意外是可以理解的,但可恢復的bug就沒什
麼意義了。既然已經約定好了,你再違背,就屬於是bug而不是意外了(比如打
不開什麼的)。很多庫都不檢查指標的合法性(除了以外,因
為總不能讓系統dump core吧),也不檢查指標是否為NULL(因為如果層層都檢
查,必定勞民傷財,乾脆讓最上面呼叫的人在呼叫之外查)。

6、選擇d+f
選f+d, 好處如下:
a以最激烈的方式,充分暴露呼叫都的錯誤!能及時修改BUG
b便於,問題出現後,直接到事故現場。比120還快!
c對於realse版的程式碼沒有任何副作用。
d以處理的代價來看 採用斷言也是編寫最小一種。
e它是多語種,多平臺所通用的方式, 如:C /C++ VB,1.4 在win ,
通吃, 便於移置!
如果在現實中,測試沒有能找到所有的BUG,那可能就要用異常來幫忙了!

當然,我也提出了我的觀點, 我支援觀點6。理由如下:
assert只在debug標誌的時候有用,而在編譯release版本的時候不起作用。
assert對於檢查硬編碼的錯誤,是非常有用的,能夠及時的查處編碼的錯
誤。比如borland c++的類庫中就有很多這樣的assert。但是assert
不是萬能的,因為有很多錯誤的發生不是完全在編譯時發生的,而是執行
時的錯誤。在release後,assert是不可能依賴的。那麼,我們就需要
exception這一機制來檢測執行時錯誤,並相應的做出處理。當然,在異常
檢測和處理過程中還有許多需要討論的問題,由於不是這一題目的範圍,
我們沒有必要繼續討論得太多,但是,提出來希望大家注意:異常不是捕
獲了就完成任務了,而要對於不同的情況,採取不同的處理辦法,千萬不
能只是捕獲,而不做任何處理,那樣和不捕獲異常沒有任何區別。

在題目剛剛提出的時候,選擇各種答案的人都有,所以,我有必要在這裡把
其他答案為什麼不能選的理由說一下。

(a) if (!pParam)
return 0;
這是很多初級員常常採取的一種方式。返回值設為0。 因為函式的返回
值往往是計算的結果,不贊成把錯誤標誌值和計算結果混在一起使用,容易
造成使用者的誤會。當然,在很多unix函式中,由於歷史原因,還存在很多
這樣子的函式,所以需要指出,不要沿用這種方式。

(b) if (!pParam)
return ERROR_PARAM;
b比a稍微好一點點,返回了一個常量或者預定義的宏。 從返回值的字面上,
呼叫者能知道發生了什麼錯誤,但是,這也不是一種好的方法。
(c) if (!pParam)
pParam = "";
...
這是最不好的方式。直接給pParam賦予空字串,然後繼續函式過程,這
容易造成不可預料的後果,是程式不穩定的根源。
(d) if (!pParam)
throw EXCEPTION_ERROR_PARAM;
丟擲異常,剛剛已經討論過了,不再贅述。
(e) if (!pParam)
MessageBox(...);
這是一種比較可笑的方式,當然也有不少人用。MessageBox是直接彈出一個
對話方塊,告訴使用者,出錯了。但是並不做任何處理,程式繼續往下,
直到出錯崩潰。呵呵
(f) assert(!pParam);
斷言,剛剛已經討論過了,不再贅述。

以上這個題目,引發了所有與會者的興趣,討論異常熱烈,最後,主持人
也給出了自己的觀點:d+f。當然這並不是標準答案,因為這一門課程
本來就沒有什麼標準答案,大家見仁見智,這個答案只是的積累。

主持人緊接著列出了"編寫優質無錯程式碼的經驗":
a.理想的和實際的編譯器
b.使用斷言
c.函式的介面設計
d.考慮風險
e.態度的問題

以上是本節的主要內容。斷言,剛剛的問題中已經討論過了,來看看其他的
內容。

理想的編譯器和實際的編譯器:

題目二:
下面memcpy函式實現有什麼問題:
Void *memcpy(void *pvTo,void *pvFrom,size_t size){
byte *pbTo=(byte *) pvTo;
byte *pbFrom=(byte *)pvFrom;
while(size -- >0);
*pbTo++= *pbFrom++;
return pvTo;
}

呵呵,粗略一看,這函式還真找不出問題來。但是仔細看看,你就會發現
while(size -- >0);
這裡多了一個分號,導致下面的*pbTo++= *pbFrom++;不是在while迴圈中
執行多次,而是隻執行了一次。當然這不是函式設計者的預期結果,而編
譯器卻不會報告錯誤,因為while(size -- >0);從語法上來講,並沒有
錯誤。這就是理想的編譯器和實際的編譯器的區別所在。

那麼,該怎麼檢查這種錯誤呢?主持人提出瞭如下辦法:
while(size -- >0) NULL; 可以加入NULL來解決空語句. 這樣子,當你需
要 while(size -- >0)
 *pbTo++= *pbFrom++;
這種形式的時候,就不會發生錯誤了,只需要用眼睛看看,就能發現了。
兩點好處 1 無冗餘程式碼,2 使人更明白。減少風險.

還有人會這麼寫
if( (n=read(....)) == 1) ....
在這裡,賦值符號=和判斷相等的符號==容易敲錯,而編譯器又檢查不出來,
可能就會有如下錯誤:
If(ch = ‘ ’)...;這也是需要注意的問題。

理想的編譯器和實際的編譯器小結:
a.把屢次出錯的合法的C習慣用法看成程式中的錯誤
b.增強編譯器的警告級別
c.使用其它的工具來檢查程式碼 如 Lint 等
d.進行單元測試
e.消除程式錯誤的最好方法是儘可能早、儘可能容易地發現錯誤,要尋求費力最小的自動查錯的方法
f.努力減少程式設計師查錯所需的技巧

使用斷言
題目三
下面函式實現,哪一個好,為什麼?
a.
char Uptolower(char ch){
if(ch >= ‘A’ && ch <= ‘Z’)
return ch+=‘a’-’A’;
return -1;
}
b.
char Uptolower(char ch){
assert(ch >= ‘A’ && ch <= ‘Z’);
if(ch >= ‘A’ && ch <= ‘Z’)
return ch+=‘a’-’A’;
return ch;
}
c.
char Uptolower(char ch){
assert(ch >= ‘A’ && ch <= ‘Z’);
return ch+(‘a’-’A’);
}
分析:
a.該函式檢查ch是否在A..Z之間,如果是,則返回相應的小寫字元,如果


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10748419/viewspace-998418/,如需轉載,請註明出處,否則將追究法律責任。

相關文章