11、出錯資訊的處理
—————————
你會處理出錯資訊嗎?哦,它并不是簡單的輸出。看下面的示例:
if ( p == NULL ){
printf ( "ERR: The pointer is NULL\n" );
}
告别學生時代的程式設計吧。這種程式設計很不利于維護和管理,出錯資訊或是提示資訊,應該統一處理,而不是像上面這樣,寫成一個“寫死”。第10條對這方面的處理做了一部分說明。如果要管理錯誤資訊,那就要有以下的處理:
/* 聲明出錯代碼 */
#define ERR_NO_ERROR 0 /* No error */
#define ERR_OPEN_FILE 1 /* Open file error */
#define ERR_SEND_MESG 2 /* sending a message error */
#define ERR_BAD_ARGS 3 /* Bad arguments */
#define ERR_MEM_NONE 4 /* Memeroy is not enough */
#define ERR_SERV_DOWN 5 /* Service down try later */
#define ERR_UNKNOW_INFO 6 /* Unknow information */
#define ERR_SOCKET_ERR 7 /* Socket operation failed */
#define ERR_PERMISSION 8 /* Permission denied */
#define ERR_BAD_FORMAT 9 /* Bad configuration file */
#define ERR_TIME_OUT 10 /* Communication time out */
/* 聲明出錯資訊 */
char* errmsg[] = {
/* 0 */ "No error",
/* 1 */ "Open file error",
/* 2 */ "Failed in sending/receiving a message",
/* 3 */ "Bad arguments",
/* 4 */ "Memeroy is not enough",
/* 5 */ "Service is down; try later",
/* 6 */ "Unknow information",
/* 7 */ "A socket operation has failed",
/* 8 */ "Permission denied",
/* 9 */ "Bad configuration file format",
/* 10 */ "Communication time out",
};
/* 聲明錯誤代碼全局變量 */
long errno = 0;
/* 列印出錯資訊函數 */
void perror( char* info)
{
if ( info ){
printf("%s: %s\n", info, errmsg[errno] );
return;
}
printf("Error: %s\n", errmsg[errno] );
這個基本上是ANSI的錯誤處理實作細節了,于是當你程式中有錯誤時你就可以這樣處理:
bool CheckPermission( char* userName )
if ( strcpy(userName, "root") != 0 ){
errno = ERR_PERMISSION_DENIED;
return (FALSE);
...
main()
if (! CheckPermission( username ) ){
perror("main()");
一個即有共性,也有個性的錯誤資訊處理,這樣做有利同種錯誤出一樣的資訊,統一使用者界面,而不會因為檔案打開失敗,A程式員出一個資訊,B程式員又出一個資訊。而且這樣做,非常容易維護。代碼也易讀。
當然,物極必反,也沒有必要把所有的輸出都放到errmsg中,抽取比較重要的出錯資訊或是提示資訊是其關鍵,但即使這樣,這也包括了大多數的資訊。
12、常用函數和循環語句中的被計算量
—————————————————
看一下下面這個例子:
for( i=0; i<1000; i++ ){
GetLocalHostName( hostname );
GetLocalHostName的意思是取得目前計算機名,在循環體中,它會被調用1000次啊。這是多麼的沒有效率的事啊。應該把這個函數拿到循環體外,這樣隻調用一次,效率得到了很大的提高。雖然,我們的編譯器會進行優化,會把循環體内的不變的東西拿到循環外面,但是,你相信所有編譯器會知道哪些是不變的嗎?我覺得編譯器不可靠。最好還是自己動手吧。
同樣,對于常用函數中的不變量,如:
GetLocalHostName(char* name)
{
char funcName[] = "GetLocalHostName";
sys_log( "%s begin......", funcName );
...
sys_log( "%s end......", funcName );
}
如果這是一個經常調用的函數,每次調用時都要對funcName進行配置設定記憶體,這個開銷很大啊。把這個變量聲明成static吧,當函數再次被調用時,就會省去了配置設定記憶體的開銷,執行效率也很好。
13、函數名和變量名的命名
————————————
我看到許多程式對變量名和函數名的取名很草率,特别是變量名,什麼a,b,c,aa,bb,cc,還有什麼flag1,flag2, cnt1, cnt2,這同樣是一種沒有“修養”的行為。即便加上好的注釋。好的變量名或是函數名,我認為應該有以下的規則:
1) 直覺并且可以拼讀,可望文知意,不必“解碼”。
2) 名字的長度應該即要最短的長度,也要能最大限度的表達其含義。
3) 不要全部大寫,也不要全部小寫,應該大小寫都有,如:GetLocalHostName 或是 UserAccount。
4) 可以簡寫,但簡寫得要讓人明白,如:ErrorCode -> ErrCode, ServerListener -> ServLisner,UserAccount -> UsrAcct 等。
5) 為了避免全局函數和變量名字沖突,可以加上一些字首,一般以子產品簡稱做為字首。
6) 全局變量統一加一個字首或是字尾,讓人一看到這個變量就知道是全局的。
7) 用匈牙利命名法命名函數參數,局部變量。但還是要堅持“望文生意”的原則。
8) 與标準庫(如:STL)或開發庫(如:MFC)的命名風格保持一緻。
14、函數的傳值和傳指針
向函數傳參數時,一般而言,傳入非const的指針時,就表示,在函數中要修改這個指針把指記憶體中的資料。如果是傳值,那麼無論在函數内部怎麼修改這個值,也影響不到傳過來的值,因為傳值是隻記憶體拷貝。
什麼?你說這個特性你明白了,好吧,讓我們看看下面的這個例程:
void
GetVersion(char* pStr)
pStr = malloc(10);
strcpy ( pStr, "2.0" );
main()
char* ver = NULL;
GetVersion ( ver );
free ( ver );
我保證,類似這樣的問題是一個新手最容易犯的錯誤。程式中妄圖通過函數GetVersion給指針ver配置設定空間,但這種方法根本沒有什麼作用,原因就是——這是傳值,不是傳指針。你或許會和我争論,我分明傳的時指針啊?再仔細看看,其實,你傳的是指針其實是在傳值。
15、修改别人程式的修養
———————————
當你維護别人的程式時,請不要非常主觀臆斷的把已有的程式删除或是修改。我經常看到有的程式員直接在别人的程式上修改表達式或是語句。修改别人的程式時,請不要删除别人的程式,如果你覺得别人的程式有所不妥,請注釋掉,然後添加自己的處理程式,必竟,你不可能100%的知道别人的意圖,是以為了可以恢複,請不依賴于CVS或是SourceSafe這種版本控制軟體,還是要在源碼上給别人看到你修改程式的意圖和步驟。這是程式維護時,一個有修養的程式員所應該做的。
如下所示,這就是一種比較好的修改方法:
/*
* ----- commented by haoel 2003/04/12 ------
*
* char* p = ( char* ) malloc( 10 );
* memset( p, 0, 10 );
*/
/* ------ Added by haoel 2003/04/12 ----- */
char* p = ( char* )calloc( 10, sizeof char );
/* ---------------------------------------- */
當然,這種方法是在軟體維護時使用的,這樣的方法,可以讓再維護的人很容易知道以前的代碼更改的動作和意圖,而且這也是對原作者的一種尊敬。
以“注釋 — 添加”方式修改别人的程式,要好于直接删除别人的程式。
本文轉自 haoel 51CTO部落格,原文連結:http://blog.51cto.com/haoel/124710,如需轉載請自行聯系原作者