對于BUG的自信
Donald E. Knuth(高德納)在TeX: The Program的前言中說:
"我相信,在1985年11月27日,TeX代碼里面的最后一個BUG已經被發現和解決了。但是,如果代碼中仍舊有BUG,我很高興付給任何第一個發現BUG的人20.48美元(這是前一個金額的兩倍,而且我計劃在一年內把它翻倍。你看,我很自信!)"
想知道后來發生了什么嗎?
在http://truetex.com/knuthchk.htm可以看到他寫出去的支票的金額是從2.56美元開始翻倍的。微基百科中關于這種支票的文章(http://en.wikipedia.org/wiki/Knuth_reward_check)說,截至2001年10月為止,他寫出去了超過兩千張這樣的支票,但是他的BUG支票是如此有名,以至于很多人把他的支票收藏起來而不是拿出去兌現(http://www.tug.org/whatis.html)。
有多少程序員在發布產品的時候可以這樣自信地聲明產品沒有問題?
遺憾的是,現在的程序員經常把發現BUG的責任推給測試人員——“不用擔心,測試人員會發現所有BUG的,這是他們的工作”。實際上,測試人員并沒有開發人員的條件,他們不可能進行源代碼級別的調試,很大程度上只能靠運氣——沒錯,是靠運氣,如果一個BUG很容易被發現,程序員不太可能自己沒有發現它——來發現BUG。
還有一些人干脆就認為BUG是不可避免的,或者認為不值得這么精益求精(參見網易虛擬社區http://p5.club.163.com/viewArticleByWWW.m?boardId=clanguage&articleId=clanguage_108eacc622169e7&boardOffset=0的討論),但是實際上防止BUG出現的最好的時機,就是在編寫代碼的時候。在編寫代碼一段時間之后,即使是編寫者本人也可能需要一段時間來理解代碼(如果不習慣寫注釋的話,這段時間會更長),更別說定位問題所在了。在編寫代碼時,如果具有良好的習慣,可以免去很多在之后消滅BUG的困難。
規范不是語法
太多人把不要使用goto奉為圣旨,從來不想去打破。他們會爭論,goto會造成難以維護的難讀的代碼,以及使編譯器無法進行優化。這兩點在很大程度上是真的,但是也有使用goto可以增加程序可讀性和效率時候。在這種情況下,遵循“不使用goto語句”規范會產生更糟糕的代碼。
一些人喜歡在成員函數后面加const,但是另外一些人沒有養成這個習慣。一個直接的結果就是,一些看起來對對象完全沒有影響的函數不能在const函數里面使用。這時候應該怎么辦?看看Paul DiLascia建議的,把this指針強行轉化為一個非const指針(http://www.microsoft.com/msj/archive/S126E.aspx)。如果函數實際上會對對象成員造成影響(例如CToolBar::GetItemRect),這也會帶來潛在危險。
為了和ANSI標準之前編寫的代碼兼容,ANSI C中的memchr函數的聲明為
void *memchr(
const void *buf,
int c,
size_t count
);
這里c是一個字符。很明顯,標準為了兼容性放棄了明確性和更強的類型檢查。如果放棄兼容性,這個函數應該聲明為如下形式
void *memchr( const void *buf, unsigned char c, size_t count);
微軟的很多代碼使用一種叫做匈牙利表示法的命名規范。這使得標識符的含義和類型更加明確——但是這是從廣義的角度來說的??紤]如下函數聲明
char *strcpy( char *strDestination, const char *strSource );
如果嚴格遵循原始的匈牙利表示法,那么兩個參數的聲明應該是pch開頭。但是以str開頭給這兩個參數更多含義:它們指向以\0為結束符的字符串
規范是用來在大部分時間里遵循,以及在可以得到更好的結果時打破的。
編譯警告的意義
智能化的編譯器開始將語法正確的語句列為警告:
while(size-->0);//注意這里有個分號 *pTo++=*pFrom++;
編譯器會報告空循環問題。
但是對于以0結尾的字符串復制
while(*pTo++=*pFrom++);
,這樣的警告是多余的。
更加常見的警告是在條件判斷語句中
if(ch='\0') EndOfString();
為了繞過這個警告,需要添加額外的運算或者語句,或者更正錯誤的賦值。
while((*pTo++=*pFrom++)!='\0')...{}if(ch=='\0')
一些程序員甚至將比較語句修改成
if('\0'==ch)
這樣作的原因顯而易見:為了減少潛在的BUG。如果你的編譯器沒有這樣的警告,那么你可以使用一些工具來檢查那些語法正確但是有潛在BUG的代碼。LintProject (http://www.codeproject.com/tools/lintproject.asp)就是其中一個。但是,良好的編程習慣還是減少BUG出現的最好的方法。
在覺得警告消息太煩人的時候,不妨想想編譯器的開發人員為什么要編寫這么多警告消息,而不是僅僅尋求關閉警告的方法。
P.S. Visual C++的默認警告等級是3級。發布軟件之前應該改成4級,之后檢查所有的編譯警告。
無處不在的斷言
使用編譯器來捕獲BUG的主意很好,Visual Studio 2005甚至會報告定義的變量不符合命名規范(Warning 1 CA1709 : Microsoft.Naming : Correct the casing of type name 'welcome'.);但是我敢打賭你檢查BUG列表的時候,你會發現只有一小部分BUG會被編譯器抓到。很多BUG在程序運行過程中很少會出現,例如內存分配失敗的問題
char* strBuffer=new char [length];
MyZeroMemory(strBuffer,length);
這段代碼在絕大多數情況下會成功,但是在虛擬內存不足的時候,Windows會報告“您的系統虛擬內存太低,WINDOWS會增加虛擬內存頁面文件的大小。在這個過程中,一些應用程序的內存請求會被拒絕”然后開始增加虛擬內存,在這個過程中,new這樣的內存分配可能會因為內存不足而失敗,而MyZeroMemory則可能會造成訪問越界。如果你足夠幸運,你會在產品發布之前發現這個BUG,否則,你的用戶會代替你發現這個BUG。要是用戶剛好沒有備份的習慣,丟失了幾十分鐘甚至是幾小時的工作進度,用戶會很生氣,后果很嚴重。
原文轉自:http://www.anti-gravitydesign.com