猜数字游戏的bug讨论,如何发现并修正bug。

作者:jhkdiy

    之前大家都看过了《C++编写猜数字游戏》的文章了,整个源代码比较简单,有C++基础就可以看
明白程序的流程,但我的目的并不是在说说简单的C++语法复习和C++的实际应用,而是在于说一
下一直被很多初学者忽略的编程问题:如何发现并修正bug。很多学编程的朋友一般只要在编译时没有
语法错误能编译运行就万事大吉了,这样的不良习惯在以后会很严重。
那么我们一般怎样发现错误呢?错误不外乎这几种情况:第一个肯定是语法错误,这个比较容易解决,
因为一般编译器都会发现这样的错误并有明显的提示,我们只要找到错误行并修正就可以了,错误一般
是无意的拼写错误,符号错误等。
    另一种情况是语法没有错误,但是程序就是没有达到自己预想的结果这一般是逻辑错误,而逻辑错误又
分为两种情况,一种是无意的逻辑错误,例如本来是想一个循环执行10次的,但是错误将结束条件写错了,而
导致执行了更多或更少,这种情况就要自己阅读源代码来解决了,因为自己了解程序的流程,所以一般都比较
容易发现。另一种情况是算法本身有问题,或更通俗的说是解决问题的方法有问题,设计思路有问题,这种情
况算是比较难发现的了。因为既不是语法错误,又不是自己的无意写错,代码更自己设想的流程都一样,但结
果就是不对。这种情况打败了很多编程人员,很多朋友在这种情况下只能到论坛发帖子了,找求救了,但很多
网友也一样爱莫能助。我个人认为这种情况还是自己研究好,当程序到了这种地步的时候就应该思考了:是我
的设计思路有问题吗?这个问题有没有其它方法可以解决?解决问题的方法通常都不止一个,当出现这种情况
的时候就应该考虑使用其它设计思路了。这也意味着一个大问题:你的代码需要重写,这对一般的小程序不成
问题,但如果重写导致整个项目的变动时就麻烦了,所以要慎重。
    还有一种情况是环境错误和系统错误。这种情况可以说简单,而有时候又不简单。这种错误的表现是程序能
正常编译,但程序一执行就说“非法内存访问”或“此内存不能read”,跟着便是程序崩溃。相信很多朋友都遇过
这种情况,程序员最痛苦的事莫过于此。新手一般懂得解决这种问题,因为他还不懂得怎样调试程序,这时到论坛
发帖求助是正确的。懂得调试的朋友就比较容易发现错误,当出现内存访问错误对话框时,可以记下导致程序错误
的指令地址,然后用调试软件载入程序,在导致程序错误的指令前下断点来执行,这时通常都会发现问题的所在。
只要修改一下代码就可以了。最后一种情况就是系统本身的问题,这种问题当你不知道原因的时候最难解决,但当
你知道原因的时候却最容易解决,这种情况就是程序的运行环境问题,一般学习语言的时候不会碰到,但编写实际
的应用软件时就会发生了,例如有些API函数只能在NT系列的系统上运行,win9x是不支持的,有很多新手不知道某个
函数的执行平台,老是发帖子问,但很多时候的回复就是简单的“该函数不能在该系统上运行”。解决这样的问题只
有找个替换的方法了,或者使用新的API,或者使用另一种方法。
    错误的表现总是多种多样的,很多时候让人琢磨不透,但当问题解决的那一刹那却无比欣喜。这就是编程,让我
欢喜让我优。当出现问题时要试着自己解决,发现错误并修正它,在这个过程中你会学习到更多有趣的知识,而你的
编程经验也将在这里积累,所以编程能力的提高不单单是编写更多的程序,同时还要懂得调试程序。
    好了,说了这么多,下面就讨论一下这个游戏存在的问题,首先这里不讨论语法错误问题,因为如果出现语法问题
证明你的基础不扎实。那么这个程序到底都有那些问题呢?在解说之前还是再看看代码吧:

/*
*   功    能:猜数字的游戏 
*   编译环境:windows2000 + Dev-c++ 4.9 
*   作    者:jhkdiy
*   电子邮件:jhkdiy_gzb@21cn.net 
*   备    注:无意中看到《Beginning C++ game programming》一书,随便浏览了一下, 
*             觉得这个游戏有点意思,所以自己完善了下。初学者用来学习一下语法
*             和提高一下兴趣还是很有意思的。该程序有BUG,大家找出来,
*             不是语法错误哦!!思考一下!  
*/

#include <cstdlib>
#include <iostream>
#include <ctime>

using namespace std;

int main(int argc, char *argv[])
{
    //产生随机数种子
    srand(time(0));             
    
    int  theNumber = rand() % 100 + 1;     //随机数控制在1-100之间
    int  tries = 0,                        //用户尝试的次数 
         guess;                            //用户输入的数字 
    char bPlayAgain;                       //是否继续游戏 
    
    cout << "\tWelcome to guess my number\n\n";
    
    do
    {
         //接受用户的输入 
         cout << "Enter a guess: ";
         cin >> guess;
         ++tries;
         
         //如果输入的数字大于产生的随机数 
         if( guess > theNumber )
         {
             cout<<"Too high!\n\n";
         }
         
         //如果输入的数字小于产生的随机数 
         if( guess < theNumber )
         {
             cout << "Too low!\n\n";
         }
         
         //猜对了 
         if( guess == theNumber )
         {
             cout << "\n\n\tVery good! you get it!" 
                  << "\tThe number is: " << theNumber << endl;
                  
             cout << "\n\n\tyou try " << tries << " times!" << endl;
             
             //是否继续游戏 
             cout << "\n\nDo you want to play again?(y/n)";
             cin  >> bPlayAgain;
             if( bPlayAgain == 'y' || bPlayAgain == 'Y')
             {
                 //清屏后继续游戏 
                 system("cls");
                 continue;
             }
             else if( bPlayAgain == 'n' || bPlayAgain == 'N')
             {
                 cout << "\nSee you next time, bye!\n";
                 break;             
             }    
             else
             {
                 cout << "\nEnter a wrong choice! program will exit!\n";
                 break;
             }
         }
         
    }while(true);
    
    //退出程序 
    system("pause");
    return EXIT_SUCCESS;
}


    程序第一次运行完全没有问题,但很多朋友很快就发现了一个bug,这个bug出现在:

             //是否继续游戏 
             cout << "\n\nDo you want to play again?(y/n)";
             cin  >> bPlayAgain;
             if( bPlayAgain == 'y' || bPlayAgain == 'Y')
             {
                 //清屏后继续游戏 
                 system("cls");
                 continue;
             }


    程序直接清屏后就继续游戏了,竟然忘了,忘了什么?当第二次猜数字猜对的时候尝试
次数明显不对,明明只用4次猜对了,它竟然说尝试了9次。再看看代码,哦,原来在继续下
一次游戏之前没有对tries变量作初始化,这个容易修正:

             //是否继续游戏 
             cout << "\n\nDo you want to play again?(y/n)";
             cin  >> bPlayAgain;
             if( bPlayAgain == 'y' || bPlayAgain == 'Y')
             {
                 //清屏后继续游戏 
                 tries = 0;
                 system("cls");
                 continue;
             }

     这下应该没问题了吧,第二次猜猜看,第三次猜猜看,咦??怎么每次猜的数字都是一样的?
再看看代码,又是刚才修改过的那段代码,程序中用来保存随机数的变量theNumber只在程序第一次
运行的时候赋值了,当第二次猜数字的时候它根本没有变,所以导致第二次以后的数字都是一样的。
好,知道问题所在后就好办了,继续修正:

             //是否继续游戏 
             cout << "\n\nDo you want to play again?(y/n)";
             cin  >> bPlayAgain;
             if( bPlayAgain == 'y' || bPlayAgain == 'Y')
             {
                 //清屏后继续游戏 
                 tries = 0;
                 theNumber = rand() % 100 + 1;     //随机数控制在1-100之间
                 system("cls");
                 continue;
             }


     好了,当程序第一次运行没问题,第二次、第三次也没问题了,自从修好后,腰不酸了,腿不疼了
嘿,还真灵。真的没问题了吗?没问题啊,程序现在运行的好好的。呵呵,大家忽略了一个事实,
我们都假定用户的输入是可信赖的。问题就在这里,大家看看这段代码:

         //接受用户的输入 
         cout << "Enter a guess: ";
         cin >> guess;
         ++tries;


     程序假定用户输入的就是我们期望的数字,但是,如果用户输入的不是数字又会如何呢?实际情况吓
我们一跳,程序无限循环,已经无法控制了。这在实际的应用软件中经常出现,这就表明很多软件设计者
都不经意地作了很多假设:只要用户照我的使用方法做程序准没错。但是很多用户就是喜欢搞破坏,喜欢
搞垮程序。我们作为设计者应该要重视这个问题,对用户的输入不作任何自以为是的假设,对输入进行严格
的检查,只有符合程序运行的输入才作处理。那么这个程序应该如何修改呢?很简单,我们要对用户的输入
进行检查,提示用户只能输入数字:

         //接受用户的输入 
         cout << "Enter a guess: ";
         cin >> guess;
         
         //输入数据类型错误,非致命错误,可清除输入缓冲区挽回! 
         if( cin.rdstate() == ios_base::failbit )
         {
             system("cls");
             cout << "Please enter numeric value!\n"<< endl;
             
             //使用clear()更改标记为正确后,同时也需要使用get()成员
             //函数清除输入缓冲区,以达到重复输入的目的。
             cin.clear();
             cin.get();
             continue;
             
         }
         
         ++tries;


    好了,再次编译运行程序,输入非数字字符,哦,程序检测到了,能正常运行。程序共
修改了三次,最后的代码如下:

/*
*   功    能:猜数字的游戏 
*   编译环境:windows2000 + Dev-c++ 4.9 
*   作    者:jhkdiy
*   电子邮件:jhkdiy_gzb@21cn.net 
*   备    注:无意中看到《Beginning C++ game programming》一书,随便浏览了一下, 
*             觉得这个游戏有点意思,所以自己完善了下。初学者用来学习一下语法
*             和提高一下兴趣还是很有意思的。该程序有BUG,大家找出来,
*             不是语法错误哦!!思考一下!  
*   修    正:第二次运行tries变量没初始化,第二次运行theNumber变量没初始化,
*             当用户输入非数字字符时程序崩溃的问题。
*/

#include <cstdlib>
#include <iostream>
#include <ctime>

using namespace std;

int main(int argc, char *argv[])
{
    //产生随机数种子
    srand(time(0));             
    
    int  theNumber = rand() % 100 + 1;     //随机数控制在1-100之间
    int  tries = 0,                        //用户尝试的次数 
         guess;                            //用户输入的数字 
    char bPlayAgain;                       //是否继续游戏 
    
    cout << "\tWelcome to guess my number\n\n";
    
    do
    {
         //接受用户的输入 
         cout << "Enter a guess: ";
         cin >> guess;
         
         //输入数据类型错误,非致命错误,可清除输入缓冲区挽回! 
         if( cin.rdstate() == ios_base::failbit )
         {
             system("cls");
             cout << "Please enter numeric value!\n"<< endl;
             
             //使用clear()更改标记为正确后,同时也需要使用get()成员
             //函数清除输入缓冲区,以达到重复输入的目的。
             cin.clear();
             cin.get();
             continue;
             
         }
         
         ++tries;
         
         //如果输入的数字大于产生的随机数 
         if( guess > theNumber )
         {
             cout<<"Too high!\n\n";
         }
         
         //如果输入的数字小于产生的随机数 
         if( guess < theNumber )
         {
             
             cout << "Too low!\n\n";
         }
         
         //猜对了 
         if( guess == theNumber )
         {
             cout << "\n\n\tVery good! you get it!" 
                  << "\tThe number is: " << theNumber << endl;
                  
             cout << "\n\n\tyou try " << tries << " times!" << endl;
             
             //是否继续游戏 
             cout << "\n\nDo you want to play again?(y/n)";
             cin  >> bPlayAgain;
             if( bPlayAgain == 'y' || bPlayAgain == 'Y')
             {
                 //清屏后继续游戏 
                 tries = 0;
                 theNumber = rand() % 100 + 1;     //随机数控制在1-100之间
                 system("cls");
                 continue;
             }
             else if( bPlayAgain == 'n' || bPlayAgain == 'N')
             {
                 cout << "\nSee you next time, bye!\n";
                 break;             
             }    
             else
             {
                 cout << "\nEnter a wrong choice! program will exit!\n";
                 break;
             }
         }
         
    }while(true);
    
    //退出程序 
    system("pause");
    return EXIT_SUCCESS;
}


     总结下,第一个和第二个错误属于逻辑错误,由于编程疏忽大意而造成的,但第三个错误
在用户不输入非数字字符前是永远不会被发现出来的,这就是程序的健壮性问题。
    希望看了这篇文章后对大家有所帮助,有所觉悟,在编程的道路上更进一步。
下面是修改后完整的代码和执行程序。

http://jhkdiy.go3.icpcn.com/code/download/gessnumber.rar

http://jhkdiy.go3.icpcn.com/code/download/gessnumberfixed.rar