劣质代码评析——《写给大家看的C语言书(第2版)》附录B之21点程序(三)
下面来考察一下main()的总体结构。
29. main() 30. { 31. int numCards; 32. int cards[52],playerPoints[2],dealerPoints[2],total[2]; 33. char ans; 34. 35. do 36. { 37. initCardsScreen(cards,playerPoints,dealerPoints,total, &numCards); 38. dealerGetsCard(&numCards,cards, dealerPoints); 39. printf("\n"); 40. playerGetsCard(&numCards,cards,playerPoints); 41. playerGetsCard(&numCards,cards,playerPoints); 42. do 43. { 44. ans = getAns("Hit or stand (H/S)?"); 45. if ( ans == 'H' ) 46. { 47. playerGetsCard(&numCards,cards,playerPoints); 48. } 49. } 50. while( ans != 'S' ); 51. 52. totalIt(playerPoints,total,PLAYER); 53. do 54. { 55. dealerGetsCard(&numCards,cards,dealerPoints); 56. } 57. while (dealerPoints[ACEHIGH] < 17 ); 58. 59. totalIt(dealerPoints,total,DEALER); 60. findWinner(total); 61. 62. ans = getAns("\nPlay again(Y/N)?"); 63. } 64. while(ans=='Y'); 65. 66. return 0; 67. } 68.
main()函数中do-while语句循环体部分的含义是这样的
35. do 36. { /*21点游戏*/ 62. ans = getAns("\nPlay again(Y/N)?"); /*询问是否继续*/ 63. } 64. while ( ans == 'Y' );
与下面写法相比,两者在逻辑上的结构差别很明显
do { /*21点游戏*/}while( getAns("\nPlay again(Y/N)?") == 'Y' ); /*询问是否继续*/
前者循环体内要做两件事,更精确地说做1.5件事情,因为“询问是否继续”没有完全做利索,一脚门里一脚门外,拖泥带水。而后面的写法在循环体内只关注一件事情,这件事没有与另一件事情搅在一起,而是分处于do-while语句的不同部分,有个清楚的分界线。
即使从这个视角看,也不能赞成原来代码使用ans 变量的写法。
既不应该把不同的事情(“21点游戏”和“询问是否继续”)搅在一起来做,也不应该把一件事(“询问是否继续”)分裂为分离的两部分在do-while语句的不同部分完成。
现在回到循环体内,看一下完整的21点游戏的模拟过程。
37. initCardsScreen(cards,playerPoints,dealerPoints,total, &numCards);
这条语句的主要目的是实现对程序相关数据的初始化。直观感觉是这个函数的参数太多了,通常标志着数据结构设计的失败。
16. void initCardsScreen(int cards[52],int playerPoints[2], 17. int dealerPoints[2], int total[2], 18. int *numCards);
这个函数类型声明写得很不规范。[]内的52、2、2、2毫无必要,代码对齐格式也很成问题。应该为:
void initCardsScreen( int cards[] ,int playerPoints[] ,int dealerPoints[] , int total[] , int *numCards ) ;
同理,对应的函数定义中
69. void initCardsScreen( int cards[52],int playerPoints[2], 70. int dealerPoints[2], int total[2], 71. int *numCards ) 72. { 73. int sub,val = 1 ; 74. char firstName[15]; 75. *numCards=52; 76. 77. for(sub=0;sub<=51;sub++) 78. { 79. val = (val == 14) ? 1 : val; 80. cards[sub] = val; 81. val++; 82. } 83. 84. for(sub=0;sub<=1;sub++) 85. { 86. playerPoints[sub]=dealerPoints[sub]=total[sub]=0; 87. } 88. dispTitle(); 89. 90. if (askedForName==0) 91. { 92. printf("What is your first name?"); 93. scanf(" %s",firstName); 94. askedForName=1; 95. printf("Ok, %s,get ready for casino action!\n\n",firstName); 96. getchar(); 97. } 98. return; 99. }
中,函数的头部同样应该做类似修改
void initCardsScreen( int cards[],int playerPoints[],int dealerPoints[], int total[], int *numCards )
因为形参不可能是数组,[]内的内容无论是多少编译器都将予以忽略。写了也是白写,写它作甚?
numCards这个形参的名称甚劣,它本来是一个指针,但却居然与它所指向的变量同名。这种写法说明代码作者缺乏最基本的编程素养。
initCardsScreen()函数的功能据代码作者描述是初始化表示52张牌的数组(写入4套1~13),清屏并显示标题。从这个功能描述就足以认定这个函数的设计非常失败——它的功能太多了。函数应该只做一件事。
函数体中的
75. *numCards=52;
一句,非常煞有介事,因为这是完全用不着的。只要在main()中的do-while语句的循环体内中写一句
numCards = 52 ;
就可以了,用函数调用的方法为变量numCards初始化是根本不值得的。
读到这里,不难发现在main()中的这些变量定义的位置也不恰当,这些变量应该在do-while语句之内定义。
do { int numCards = 52 ;int cards[52],playerPoints[2],dealerPoints[2],total[2];/*21点游戏*/}while( getAns("\nPlay again(Y/N)?") == 'Y' ); /*询问是否继续*/
变量的定义应该尽量局部化。
77. for(sub=0;sub<=51;sub++) 78. { 79. val = (val == 14) ? 1 : val; 80. cards[sub] = val; 81. val++; 82. }
这个写法很业余,而且51是一个Magic Number,14也是。专业一点的写法是
for ( sub = 0 ; sub < 52 ; sub++ ){cards [ sub ] = sub % 13 + 1 ;}
所以val这个变量也同样是多余的。
84. for(sub=0;sub<=1;sub++) 85. { 86. playerPoints[sub]=dealerPoints[sub]=total[sub]=0; 87. }
“sub<=1”的写法很业余,专业人员一般写为“ sub < 2 ”。
“playerPoints[sub]=dealerPoints[sub]=total[sub]=0;”这个写法通常会受到非议,比较讲究风度的程序员一般这样写:
playerPoints[sub] = dealerPoints[sub] = total[sub] = 0 ;
还应该注意到的是,这几个数组的各个元素由于初始化为0,其实也不用通过函数调用这么复杂的方法来实现,最简洁初始化方法是在main()中直接初始化:
do { int numCards = 52 ;int cards[52] , playerPoints[2] = { 0 } ,dealerPoints[2] = { 0 } ,total[2] = { 0 } ;/*21点游戏*/}while( getAns("\nPlay again(Y/N)?") == 'Y' ); /*询问是否继续*/
结论不难得出:变量定义位置不当,会让代码笨拙无比,会给自己带来很多麻烦。现在也可以确认initCardsScreen()这个函数参数过多,实际上只需要对cards[]数组初始化。
88. dispTitle();
这行代码的作用据作者说是清屏,但是这个函数调用在initCardsScreen()这个函数中明显是一种错位。从这个函数的定义看
215. void dispTitle(void) 216. { 217. int i = 0 ; 218. while(i<25) 219. { 220. printf("\n"); 221. i++; 222. } 223. printf("\n\n*Step right up to the Blackjack tables*\n\n"); 224. return ; 225. }
其作用是输出了25个新行字符,然后输出字符串"\n\n*Step right up to the Blackjack tables*\n\n"。注意,此时光标移动到屏幕的左下方,而真正的清屏,光标是在屏幕的左上方。所以作者认为自己写了一个“比通常使用的清屏函数更加特别的清屏函数”,显然只是一种一厢情愿的错觉,他写得太“特别”了,已经特别到了算不上是清屏函数的程度了。
即使纯粹从写法上来讲,这个函数写得也很拙劣。下面的写法要整洁干净得多。
void dispTitle( void ) {int i ;for( i = 0 ; i < 25 ; i ++ ){putchar('\n');}puts("\n\n*Step right up to the Blackjack tables*\n"); }
现在回到initCardsScreen()函数定义部分。
90. if (askedForName==0) 91. { 92. printf("What is your first name?"); 93. scanf(" %s",firstName); 94. askedForName=1; 95. printf("Ok, %s,get ready for casino action!\n\n",firstName); 96. getchar(); 97. }
这段代码的毛病很多。首先,这段代码根本就不应该出现在initCardsScreen()函数定义之中,因为这和初始化没关系。
其次,即使出现,也应该像dispTitle()那样抽象为一个函数为好。
第三,现在终于弄清代码作者设置askedForName这个外部变量的用意了,但这种用意不但愚蠢而且笨拙。因为这个事情完全应该安排在main()中进行,大致的写法如下
int main(void) { char firstName[15];dispTitle(); /*这里读入firstName*/do{/*21点游戏*/}while( getAns("\nPlay again(Y/N)?") == 'Y' ); /*询问是否继续*/return 0; }
完全没有必要笨拙且愚蠢地动用危险的外部变量,那种写法完全是初学者的水准,写在书上给其他初学者做为示范是极其不负责任的行为。
看到这里有些无语……,还能说什么呢?用星爷的话来形容,“失败中的失败”。
第四,作者声称
96. getchar();
会招致编译器的警告,并要读者心安理得地忽视这个警告(ignore compiler warning here)。说实话,我见过这种由于单独调用getchar()函数而不使用其返回值的警告,但是要读者忽视警告则是一种误导和教唆。
这里如果有警告,应该用下面方法去除:
(void) getchar();
转载于:https://www.cnblogs.com/pmer/p/3179115.html
劣质代码评析——《写给大家看的C语言书(第2版)》附录B之21点程序(三)相关推荐
- 劣质代码评析——《写给大家看的C语言书(第2版)》附录B之21点程序(一)
<写给大家看的C语言书(第2版)>是邮电社图灵公司引进翻译的一本C语言入门书,这是一本垃圾书.搞不清图灵为什么引进了这样一本垃圾书.该书作者基本不懂得C编程技术,书中误导.错谬比比皆是. ...
- 劣质代码评析——《写给大家看的C语言书(第2版)》附录B之21点程序(二)
下面继续分析这个代码.为了便于说明问题,为代码添加了行号. 0. #include <stdio.h> 1. #include <time.h> 2. #include < ...
- 写给大家看的Web设计书:第3版(世界级设计大师指点迷津)(全彩印刷)
写给大家看的Web设计书:第3版(世界级设计大师指点迷津)(全彩印刷) 基本信息 原书名: The Non-Designer's Web Book, 3rd Edition 原出版社: Peachpi ...
- 代码是写给人看的,请C/C++过来的程序员们多学习软件工程
博客园新闻里面有个<让人抓狂的代码> http://news.cnblogs.com/n/156507/#bottom 里面的多数都是同意的. 但是其中第一条我是坚决反对的. 1. 确保这 ...
- 代码是写给人看的还是写给机器看的?
在大部分的情况下我会认为代码是写给人看的.虽然代码最后的执行者是机器,但是实际上代码更多的时候是给人看的.我们来看看一段代码的生命周期:开发 --> 单元测试 --> Code Revie ...
- 《写给大家看的Web设计书(第3版)》即将上市
<写给大家看的设计书>姊妹篇--<写给大家看的Web设计书(第3版)> 即将上市.它是Robin Williams的又一本设计书. 也许大家对Robin Williams的畅销 ...
- 写给大家看的Web设计书
以前自己做的界面或是展示的PPT,总觉得不协调,生搬硬套.也许看看设计方面的书能有所改善. <写给大家看的Web设计书>,面向的是非技术的设计人员,图例丰富,内容受用,读起来很愉快. 基本 ...
- 读书笔记—写给大家看的PPT设计书
作者:[美]Robin Williams 第一部分 写在设计之前 在现实生活中你可以表现得不可思议地愚蠢,你的话可以让人听得毫无兴致,昏昏欲睡,但是在这个演讲的舞台上,你却是一个明星!同时你还承担着让 ...
- 《写给大家看的Web设计书》读书笔记
这好像是一个系列的书,都叫<写给大家看的*设计书>,不过感觉这本书介绍的东西有点老,不过排版还是很精致的,老外做东西还是认真. 这里只想记录一下设计的原则,其实以前在别的书中也看过,不过怎 ...
最新文章
- 全球项目多区域数据同步问题解决方案
- 解决Matplotlib图表不能在Pycharm中显示的问题:使用Anaconda
- 操作系统中,进程与线程怎么设计的?
- Sql语句在线转java bean https://www.bejson.com/othertools/sql2pojo/
- (最优解法)46行代码AC_HDU1242 Rescue(DFS解法+BFS解法)
- Python面试题大全(三):Web开发(Flask、爬虫)
- 1 Selenium Python 基础
- android设计一个多线程和画图的程序小球,Android开发之多线程中实现利用自定义控件绘制小球并完成小球自动下落功能实例...
- 史上最全分布式数据库概述
- iOS设计模式-适配器
- Centos 6.5下NIS服务安装配置
- Uber如何使用Mesos的?答曰:和Cassandra一起用
- Linux---->进程-基础
- BugKu CTF(杂项篇MISC)--Pokergame
- 等效距离计算公式_等效焦距换算(等效焦距的计算公式)
- UE4 Slate四 SlateUI如何做动画
- mount gives unknown filesystem type 'vboxsf' 解决方法
- 迅为RK3568开发板Debian系统安装ToDesk
- 探针台选型的注意事项有哪些
- 【数据挖掘】 kmeans聚类算法的原理与实现 (使用matlab)