Write Solid Code(一) 假想的编译器

来源:互联网 发布:cab软件下载专区 编辑:程序博客网 时间:2024/05/16 03:15

     PS:最近晚上图书馆回来基本上就洗漱上床了,看原微软高级工程师Stephen A. Maguire写的牛书《Write C Solid Code》,尝试着把它翻译了一下。翻译过程中的参考资料主要是来自于这里的一个资源:http://d.download.csdn.net/down/1281339/feiyang720

  /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    花一点点时间考虑下这个:如果编译器可以明确指出你代码中的每个问题,那么你的程序还会频繁的出现bug么?我不仅仅是说格式错误,也包括了每个问题,不管它们是怎么产生的。

  假设你有一个关于边界的bug(注:作者说的是off-by-one,就是多一或者少一的问题)并且编译器可以以某种方式检测到它然后给你一个类似这样的错误(提示):

                            ->line: 23: while(i<=j)

                           边界错误:这里应该是'<'

    或者,它会发现你算法中的错误:

                           ->line: 42 : int itoa(int i,char* str)

                           算法错误:当i是-32768时itoa函数将(调用)失败

    再或者,假设,当你在(给函数)传递很糟糕的参数时,它会提示你:

                           ->line: 318: strCopy=memcpy(malloc(length),str,length);

                          无效的参数:当malloc返回NULL时,memcpy将失败

     呃,要求编译程序能够做到这一程度似乎有点过分。但如编译程序真能做到这些,可以想象编写无错程序会变得多么容易。那简直是小事一桩,和当前程序员的一般作法真没法比。

     如果你拿着一个间谍卫星上的摄像机对着典型的软件开发室,你将会发现程序员们正弓着身子趴在键盘上跟踪错误,在其旁边,你会发现测试者正在对最近的内部发行(软件)进行残酷的测试,轮番轰炸式地输入大量的数据以求找出新的bug。你甚至可能会发现测试者正在测试,以保证没有一个过去曾经出现的bug偷偷溜进代码里。如果你觉得和用理想中的编译器来追踪错误相比,用这种方式找bug要付出很多,那么,你对了。而且,它(不仅需要付出很多)而且需要很多运气。

     运气?

     是的,运气。当一个测试者发现一个bug,难道不是因为他或者她碰巧,发现了(代码中)一些数字错了,或者未预料的特征,或者程序崩溃了?再看看(上文提到的)理想编译器的编译错误(提示),如果程序运行时跳过了那个问题,那么测试者还会发现那个关于边界的bug么?另外的两个问题呢?

     这似乎听起来很让人恐惧,但是测试者对程序大量输入数据,期望那些bug可以自己暴露出来。“是的,可是我们的测试者更加高明,他们使用代码覆盖工具,自动测试套件,随机数程序,显示快照,还有其他一堆的工具。”这可能是真的,但是看看这些工具都做了什么吧。覆盖分析告诉测试者程序的哪个部分还没有被测试,测试者利用这个信息又设计出新的数据输入到你的程序。至于其它的工具无非都是“输入数据、观察结果”这一策略的自动化。

      别误会我,我不是说测试者做的不对,我只是说很难把程序当做黑盒来测试因为一个测试者能做的全部就是向程序输入数据然后观察程序的反应。这就好比是试图断定一个人是不是神经病。你问问题,你得到回答,然后你判断。最终,你却不确定你的判断是否是对的,因为你不知道被你问问题的那个人的脑袋里在想什么,你总是疑惑“我问了足够的问题了么?我问了最恰当的问题了么?”

   不要依赖黑盒测试,试着模拟假想的编译器所做的那样。不要寄希望于运气,而是抓住每一个机会来自动化的捕获bug。

   你上一次看到优秀字处理器的广告是在什么时候?如果(广告)是Madison Avenue的那些家伙写的话,那么它很可能听起来是这样:“不管你是写便条给孩子们的老师,或者为下期的《Great American Novel》写稿,  WordSmasher都可以帮您处理,毫不费力地。捕捉到不经意间溜进您的杰作里的拼写错误,它有着令人吃惊的233000的词汇量---比同类产品足足多了51000个。赶快到经销商那里去买一份。 WordSmasher是继圆珠笔之后最具革命性的书写工具。

  作为用户,我们受到市场宣传的影响而形成的思维定势使我们相信,拼写词典的词汇量越大越好。但是,这不是真的。你可以在简装本里发现像em,abel,si之类的单词,但是当me,able,is是如此常见的时候,你确实想让拼写检查器也认为这些单词(em,abel,si)是正确的么?如果你在我写的句子里面看到suing,其本意很可能是与之风马牛不相及的using,suing是不是一个真正的单词并不重要,重要的是,在我的句子里,它是一个错误。

  幸运的是,高质量拼写检查器允许你从它的词典库中的删除这些很恼人的单词(比如em)以便你可以对其他除此之外的单词设置合法标志。好多编译器和这个一样:它们允许你将其他合法的惯用C语句作为错误因为这些惯用语句是如此普遍的被误用。这样的编译器检测到如下所示的while循环中放错位置的分号(错误):

 

     /* memcpy -- copy a nonoverlapping memory block  */

  void* memcpy(void* pvTo,void* pvFrom,size_t size)

   {

     byte* pbTo=(byte* )pvTo;

     byte* pbFrom=(byte* )pvFrom;

     while(size-->0);

         *pbTo++=*pbFrom++;

     return (pvTo);

   }

  你可以辨别下缩进,发现那个分号是个错误,可是在编译器看来,那是个循环体为空的while语句,这可是完全合法的。现在你知道有些时候你想要空语句而有时候不需要。为了捕获不需要的空语句,编译器常常会提供一个可选的警告项,就是如果你开启它,当你遇到类似于这样的bug时编译器会自动提醒你。而当你真正想使用空语句的时候,你可以用编译器建议的解决方案来手工关闭编译器的警告,(这些解决方案)比如,使用一个可优化(注:作者说的应该是指便于代码的重用和强的可移植性等)的常量表达式(比如NULL;),或者使用空代码块{},或者是使用其他的解决方案。我在这里使用{}

   char* strcpy(char* pchTo,char* pchFrom)

{

   char* pchStart=pchTo;

   while(*pchTo++=*pchFrom++)

        {}

   return (pchStart);

}

  结果呢,你获得了空语句提供的灵活性,编译器则会自动的将无意的空语句视为错误。不允许这种空语句的使用和从你的拼写字典中删除zeros没什么不同,因为你想统一使用同一种表达形式(zeroes是可选的)。

   还有一个常见的问题是无意的赋值。C是灵活的,它允许你在任何可以写表达式的地方使用赋值语句,但是如果你不小心,那么这额外的灵活性会是让你犯错误。看看这个很常见的bug:

    if(ch='t')

         ExpandTab();

   虽然很明显代码是想比较ch和tab字符,可是它实际上是把该字符赋值给了ch,显然编译器不会生成一个错误因为该代码是合法的C语句。

   一些编译器通过让你禁用在&& //表达式和if,for,while语句的控制表达式中的简单赋值来帮助你捕获这类bug,这个特征背后的思想上是,如果一个程序员灾难性的用=代替了==,那么不会有什么糟糕的结果,因为它非常可能就是这五种情形之一(注:指&& // if  for  while)。

原创粉丝点击