《寫給大家看的C語言書(第2版)》是郵電社圖靈公司引進翻譯的一本C語言入門書,這是一本垃圾書。搞不清圖靈為什么引進了這樣一本垃圾書。該書作者基本不懂得C編程技術,書中誤導、錯謬比比皆是。
該書的附錄B給出了一個21點游戲的代碼,這是一個很糟糕的C程序,毛病很多,實在不足以為初學者以示范,當反面教材還差不多。考慮到其中的很多毛病初學者也有,所以拿來在此評析一番。
首先,這段程序可讀性極差,甚至可以說是亂作一團。連我這種閱讀代碼能力很強的人看起來都很費勁,初學者根本就沒有可能看懂。
亂作一團和可讀性還不是一回事。可讀性指的是代碼晦澀難懂,亂作一團則是指不顧起碼的編程規范。譬如代碼沒有基本的縮進
do { ans = getAns("Hit or stand (H/S)?"); if ( ans = 'H' ) { playerGetsCard(&numCards,cards, playerPoints); } }while(ans != 'S' );
任意地截斷語句或聲明
do { initCardsScreen(cards,playerPoints,dealerPoints, total, &numCards); dealerGetsCard(&numCards,cards, dealerPoints);
代碼本來就夠亂的了,又塞進了一大堆更亂更不規范的注釋。這些注釋其實是應該翻譯過來的,但是很可惜譯者偷懶並沒有翻譯,對於國內初學者來說,這就更加混亂。混亂的代碼和混亂的注釋糾結在一起,整個源程序慘不忍睹。
為了不折磨大家眼球,下面以刪除了注釋的代碼為基礎評析。大家也可以順便比較一下(http://ishare.iask.sina.com.cn/f/20886221.html),看看這些注釋是不是起了很壞的作用。如果感覺這里刪除了注釋的代碼更清楚一些,那么無疑原書上的注釋是垃圾。

#include <stdio.h> #include <time.h> #include <ctype.h> #include <stdlib.h> #define BELL '\a' #define DEALER 0 #define PLAYER 1 #define ACELOW 0 #define ACEHIGH 1 int askedForName = 0; void dispTitle(void); void initCardsScreen(int cards[52],int playerPoints[2], int dealerPoints[2], int total[2], int *numCards); int dealCard(int * numCards,int cards[52]); void dispCard(int cardDrawn,int points[2]); void totalIt(int points[2],int tatal[2],int who); void dealerGetsCard(int *numCards,int cards[52], int dealerPoints[2]); void playerGetsCard(int *numCards,int cards[52], int playerPoints[2]); char getAns(char mesg[]); void findWinner(int total[2]); main() { int numCards; int cards[52],playerPoints[2],dealerPoints[2],total[2]; char ans; do { initCardsScreen(cards,playerPoints,dealerPoints, total, &numCards); dealerGetsCard(&numCards,cards, dealerPoints); printf("\n"); playerGetsCard(&numCards,cards,playerPoints); playerGetsCard(&numCards,cards,playerPoints); do { ans = getAns("Hit or stand (H/S)?"); if ( ans = 'H' ) { playerGetsCard(&numCards,cards, playerPoints); } }while(ans != 'S' ); totalIt(playerPoints,total,PLAYER); do{ dealerGetsCard(&numCards,cards,dealerPoints); }while (dealerPoints[ACEHIGH] < 17 ); totalIt(dealerPoints,total,DEALER); findWinner(total); ans=getAns("\nPlay again(Y/N)?"); }while(ans=='Y'); return ; } void initCardsScreen(int cards[52],int playerPoints[2], int dealerPoints[2], int total[2], int *numCards) { int sub,val = 1 ; char firstName[15]; *numCards=52; for(sub=0;sub<=51;sub++){ val = (val == 14) ? 1 : val; cards[sub] = val; val++; } for(sub=0;sub<=1;sub++) { playerPoints[sub]=dealerPoints[sub]=total[sub]=0;} dispTitle(); if (askedForName==0) { printf("What is your first name?"); scanf(" %s",firstName); askedForName=1; printf("Ok, %s,get ready for casino action!\n\n", firstName); getchar(); } return; } void playerGetsCard(int *numCards,int cards[52], int playerPoints[2]) { int newCard; newCard = dealCard(numCards, cards); printf("You draw:"); dispCard(newCard,playerPoints); } void dealerGetsCard(int *numCards,int cards[52], int dealerPoints[2]) { int newCard; newCard = dealCard(numCards,cards); printf("The dealer draws:"); dispCard(newCard,dealerPoints); } int dealCard(int * numCards,int cards[52]) { int cardDrawn,subDraw; time_t t; srand(time(&t)); subDraw = (rand()%(*numCards)); cardDrawn = cards[subDraw]; cards[subDraw] = cards[*numCards -1]; (*numCards)-; return cardDrawn; } void dispCard(int cardDrawn, int points[2]) { switch(cardDrawn){ case(11): printf("%s\n","Jack"); points[ACELOW] += 10; points[ACEHIGH] += 10; break; case(12): printf("%s\n","Queen"); points[ACELOW] += 10; points[ACEHIGH] += 10; break; case(13): printf("%s\n","King"); points[ACELOW] += 10; points[ACEHIGH] += 10; break; default : points[ACELOW] += cardDrawn; if(cardDrawn==1) { printf("%s\n","Ace"); points[ACEHIGH]+= 11; } else { points[ACEHIGH]+=cardDrawn; printf("%d\n",cardDrawn); } } return ; } void totalIt(int points[2],int total[2],int who) { if ((points[ACELOW] == points[ACEHIGH])|| (points[ACEHIGH] > 21 )) { total[who] = points[ACELOW];} else { total[who] = points[ACEHIGH];} if (who == PLAYER ) {printf("You have a total of %d\n\n", total[PLAYER]);} else {printf("The house stands with a total of %d\n\n", total[DEALER]);} return; } void findWinner(int total[2]) { if ( total[DEALER] == 21 ) { printf("The house wins.\n"); return ;} if ( (total[DEALER] > 21) && (total[PLAYER] > 21)) { printf("%s", "Nobody wins.\n"); return ; } if ((total[DEALER] >= total[PLAYER])&& (total[DEALER] < 21)) { printf("The house wins.\n"); return ; } printf("%s%c","You win!\n",BELL); return; } char getAns(char mesg[]) { char ans; printf("%s", mesg); ans = getchar(); getchar(); return toupper(ans); } void dispTitle(void) { int i = 0 ; while(i<25) { printf("\n"); i++; } printf("\n\n*Step right up to the Blackjack tables*\n\n"); return ; }
然而盡管去除了臟、亂、差的注釋,代碼依然很糟糕,例如
{ printf("\n"); i++; }
更拙劣的是這個
{ points[ACEHIGH]+=cardDrawn; printf("%d\n",cardDrawn); } }
注意到中間有個小三(“}”)嗎?這風格簡直丑瘋了,只好再整理一下。

#include <stdio.h> #include <time.h> #include <ctype.h> #include <stdlib.h> #define BELL '\a' #define DEALER 0 #define PLAYER 1 #define ACELOW 0 #define ACEHIGH 1 int askedForName = 0; void dispTitle(void); void initCardsScreen(int cards[52],int playerPoints[2], int dealerPoints[2], int total[2], int *numCards); int dealCard(int * numCards,int cards[52]); void dispCard(int cardDrawn,int points[2]); void totalIt(int points[2],int tatal[2],int who); void dealerGetsCard(int *numCards,int cards[52], int dealerPoints[2]); void playerGetsCard(int *numCards,int cards[52], int playerPoints[2]); char getAns(char mesg[]); void findWinner(int total[2]); main() { int numCards; int cards[52],playerPoints[2],dealerPoints[2],total[2]; char ans; do { initCardsScreen(cards,playerPoints,dealerPoints,total, &numCards); dealerGetsCard(&numCards,cards, dealerPoints); printf("\n"); playerGetsCard(&numCards,cards,playerPoints); playerGetsCard(&numCards,cards,playerPoints); do { ans = getAns("Hit or stand (H/S)?"); if ( ans == 'H' ) { playerGetsCard(&numCards,cards,playerPoints); } } while( ans != 'S' ); totalIt(playerPoints,total,PLAYER); do { dealerGetsCard(&numCards,cards,dealerPoints); } while (dealerPoints[ACEHIGH] < 17 ); totalIt(dealerPoints,total,DEALER); findWinner(total); ans = getAns("\nPlay again(Y/N)?"); } while(ans=='Y'); return ; } void initCardsScreen( int cards[52],int playerPoints[2], int dealerPoints[2], int total[2], int *numCards ) { int sub,val = 1 ; char firstName[15]; *numCards=52; for(sub=0;sub<=51;sub++) { val = (val == 14) ? 1 : val; cards[sub] = val; val++; } for(sub=0;sub<=1;sub++) { playerPoints[sub]=dealerPoints[sub]=total[sub]=0; } dispTitle(); if (askedForName==0) { printf("What is your first name?"); scanf(" %s",firstName); askedForName=1; printf("Ok, %s,get ready for casino action!\n\n",firstName); getchar(); } return; } void playerGetsCard(int *numCards,int cards[52],int playerPoints[2]) { int newCard; newCard = dealCard(numCards, cards); printf("You draw:"); dispCard(newCard,playerPoints); } void dealerGetsCard(int *numCards,int cards[52],int dealerPoints[2]) { int newCard; newCard = dealCard(numCards,cards); printf("The dealer draws:"); dispCard(newCard,dealerPoints); } int dealCard(int * numCards,int cards[52]) { int cardDrawn,subDraw; time_t t; srand(time(&t)); subDraw = (rand()%(*numCards)); cardDrawn = cards[subDraw]; cards[subDraw] = cards[*numCards -1]; (*numCards)-; return cardDrawn; } void dispCard(int cardDrawn, int points[2]) { switch(cardDrawn) { case(11): printf("%s\n","Jack"); points[ACELOW] += 10; points[ACEHIGH] += 10; break; case(12): printf("%s\n","Queen"); points[ACELOW] += 10; points[ACEHIGH] += 10; break; case(13): printf("%s\n","King"); points[ACELOW] += 10; points[ACEHIGH] += 10; break; default : points[ACELOW] += cardDrawn; if(cardDrawn==1) { printf("%s\n","Ace"); points[ACEHIGH]+= 11; } else { points[ACEHIGH]+=cardDrawn; printf("%d\n",cardDrawn); } } return ; } void totalIt(int points[2],int total[2],int who) { if ( (points[ACELOW] == points[ACEHIGH]) ||(points[ACEHIGH] > 21 )) { total[who] = points[ACELOW]; } else { total[who] = points[ACEHIGH]; } if (who == PLAYER ) { printf("You have a total of %d\n\n", total[PLAYER]); } else { printf("The house stands with a total of %d\n\n", total[DEALER]); } return; } void findWinner(int total[2]) { if ( total[DEALER] == 21 ) { printf("The house wins.\n"); return ; } if ( (total[DEALER] > 21) && (total[PLAYER] > 21) ) { printf("%s", "Nobody wins.\n"); return ; } if ((total[DEALER] >= total[PLAYER])&& (total[DEALER] < 21)) { printf("The house wins.\n"); return ; } printf("%s%c","You win!\n",BELL); return; } char getAns(char mesg[]) { char ans; printf("%s", mesg); ans = getchar(); getchar(); return toupper(ans); } void dispTitle(void) { int i = 0 ; while(i<25) { printf("\n"); i++; } printf("\n\n*Step right up to the Blackjack tables*\n\n"); return ; }
這回勉強能看得下去了。現在不難發現main()中
return ;
實際上是
return 0;
之誤。
在dealCard()函數中,也存在一個明顯的錯誤
(*numCards)-;
實際上應該是
(*numCards)--;
或
--*numCards;
現在來閱讀代碼。首先看到的是
int askedForName = 0;
這個外部變量看起來很別扭。從21點這個問題來看,怎么也不會想到程序會需要這樣一個名為askedForName 的變量,對於整個問題來講,這個變量是無關痛癢的雞毛蒜皮。把這種雞毛蒜皮性質的變量提升到外部變量這樣重要的代碼地位,就如同把家里一件可能幾十年還用不到一次的小東西布置在客廳顯眼位置一樣荒唐。
那么作者為什么要設置這個外部變量呢?估計有兩種可能,一種是缺乏足夠的代碼寫作技能,某段代碼實在寫不下去了,無可奈何地用了一個外部變量。另一種情況是寫代碼之前缺乏充分縝密的構思,思路有漏洞,寫到中間時才發現,只好用這種辦法來補救。后一種情況比前一種更糟糕。這就像蓋房子一樣,蓋到中間才發現繼續蓋下去,房子的一個牆角就會蓋到水塘里一樣,之后匆忙修改圖紙,房子無法按原計划形狀蓋成只好不顧了。
實際上這時應該重新思考,重寫代碼,而不是修修補補。
(未完待續)