小心保護你的函數
假如今天你寫了一個階乘函數:
unsigned int
Factorial ( unsigned int number )
{
return ( number != 1 ) ? ( number * Factorial( number - 1 ) ) : 1
}
這個規格是別人給你的,而你也知道這個函數無法處理很大的輸入,但規格書上並沒有要求任何例外處理,似乎是高層覺得天底下沒有笨蛋會給這個函數太大的數字,殊不知對這個函數來說12就已經是極限了,看樣子你只好自己動手來保護她:
unsigned int
Factorial ( unsigned int number )
{
assert( number <= 12 );
return ( number != 1 ) ? ( number * Factorial( number - 1 ) ) : 1
}
這下子你就Happy囉,萬一你沒下斷言而傳出錯的數字的話,說不定還會有人質疑你的函數哩,現在要是程式垮了,大家就可以來找笨蛋囉。
函式輸入的檢查還是驗證?
有天你寫了個用字串作為亂數種子算出亂數的函數:
int
Random( const char * seed )
{
if ( seed ) { puts("seed can not be NULL"); exit(1); }
...
return r;
}
亂數可是很難處理的呢,寫完函數後你為自己感到很驕傲,這時大家卻發現整個程式有50%的時間都耗在你的函數上,於是大家告訴你用if驗證太嚴格了,這個函數的輸入其實很固定,只要在編譯的時候檢查一下就行了,Realse版時並不需要真正的驗證,所以:
int
Random( const char * seed )
{
assert( seed );
...
return r;
}
你把函數改成用斷言檢察,在Debug版時找到了所有的問題,而在Realse版就拔掉檢察節省時間,最後你們的程式玩美無錯,還變得飛快!
整數陣列同除以其開頭
把 [ 2, 4, 6, 8, 10 ] 變成 [ 1, 2, 3, 4, 5 ] 你相信會有人做錯嗎?
好吧,我就做錯過,作法是這樣的:
int numbers[ 5 ] = { 2, 4, 6, 8, 10 };
int idx;
for ( idx = 0; idx < 5; idx++ )
{
numbers[ idx ] /= numbers[ 0 ];
}
有問題嗎?有,而且很嚴重,這個問題我花了很多時間才找出來,或許當初加個前置條件( pre-condition )事情就會輕鬆多了:
int idx;
for ( idx = 0; idx < 5; idx++ )
{
assert(
numbers[ 0 ] != 1 || idx > 0 );
numbers[ idx ] /= numbers[ 0 ];
}
前置條件跟程式契約很像,不過她還有後置條件( post-condition )跟一些理論背書,詳請請看FLOLAC課程中講授的Hoare logic。
最後來提供一個我個人平常用的斷言技巧,看似簡單卻能有條供更多訊息:
assert( condition && "error message" );
沒有留言:
張貼留言