论坛: 编程破解 标题: 猜数字游戏的bug讨论,如何发现并修正bug。 复制本贴地址    
作者: jhkdiy [jhkdiy]    版主   登录
猜数字游戏的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

地主 发表时间: 07-04-11 19:39

回复: jhkdiy [jhkdiy]   版主   登录
晕,大家都去那里了,几天都没个回复的···

B1层 发表时间: 07-04-14 23:15

回复: 月之御者 [kert_t8]   论坛用户   登录
:) 我找出来两个

B2层 发表时间: 07-04-15 20:21

回复: 处女座的沙加 [virgoshaka]   论坛用户   登录
太长了。。。。

B3层 发表时间: 07-04-23 02:42

回复: 处女座的沙加 [virgoshaka]   论坛用户   登录
还是看完了。。。不错
不过我觉得学习的话还是不必太关注输入方面的代码健壮性的问题。。。
因为这个太复杂了。。。不是两三行代码可以解决的。。。
比如这个最后的程序,如果随机数生成50的话,我输入kk50,程序还是猜中了。。。但这显然不是我们的原意。
呵呵,个人意见。


B4层 发表时间: 07-04-23 03:20

回复: jhkdiy [jhkdiy]   版主   登录
呵呵,说的没错,事实上不可能检测到并控制所有的非法输入。不然windows也没那么多漏洞了。这里只是抛砖引玉地介绍一下平时编程序的错误倾向。不过还是建议大家要以严谨的态度来写程序。不要丢三落四。

B5层 发表时间: 07-04-25 10:06

回复: SysHu0teR [syshunter]   版主   登录
事实上,在我所写过的有限的垃圾代码中,大多需要交互的命令行程序,都是把输入作为字符串来处理的,也记不清是哪位大牛曾经告诉过我的。总之很感激他。

jhkdiy,很佩服你的钻研劲头与热心,加油!

B6层 发表时间: 07-05-02 17:24

回复: jhkdiy [jhkdiy]   版主   登录
谢谢SysHu0teR的赏识,唉,到现在都还没找到工作,信心大减了,都不知道还能不能干这一行了。

B7层 发表时间: 07-05-03 17:51

论坛: 编程破解

20CN网络安全小组版权所有
Copyright © 2000-2010 20CN Security Group. All Rights Reserved.
论坛程序编写:NetDemon

粤ICP备05087286号