什么是BUG?简而言之,该程序没有按我们预期的那样运行。我更喜欢将BUG分为两类:会崩溃的和不会崩溃的。在正常的编程实践中,BUG和Crash往往是基本等同的。我们在解决Crash错误上投入了大量精力。至于没有崩溃的BUG,好像也没有太多人关注。然而,事实上,那些令人心痛的“天坑”,往往是没有崩溃的BUG造成的,比如前段时间的OpenSSL心血来潮。你为什么这么说?慢慢地听我说。如何合理制造BUGCrashBUG,用程序的死亡来证明你的程序有问题,你必须抓紧时间解决程序的问题。不崩溃的bug就像一个善于撒谎的人,假装自己能正常运行,让整个程序运行在不稳定的状态。虽然外表看起来还好(没有撞车),但里面已经烂透了。一旦报告问题,往往是致命的,比如OpenSSL的心脏出血。这就是前人总结的“死程序不说谎”。崩溃并不可怕。可怕的是程序运行不稳定,没有Crash。如果程序仍然操作数据,危害将是灾难性的。所以请放心让程序崩溃,因为当它崩溃时,你还有机会改正你的错误。如果没有Crash,可以收集整个程序和产品。因此,合理创造“BUG”的原则之一也是最大的原则:尽量创造崩溃bug,减少没有崩溃的bug数量,如果可能的话,将没有崩溃的bug转化为崩溃bug,以便于搜索。NSAssert大家应该都不陌生,它的名字叫“Assertion”。断言是在开发过程中使用的代码(通常是子例程或宏),它会导致程序在运行时检查自身。断言为真意味着程序运行良好,而断言为假意味着它在代码中发现了意想不到的错误。断言对于大型、复杂的程序或需要极高可靠性的程序特别有用。而当断言为假时,几乎所有系统的处理策略都是让程序死掉,也就是崩溃。为了您的方便,程序有问题。断言实际上是“防御性编程”的常用手段。防御性编程的主要思想是,一个子程序不应该因为传入错误的数据而被破坏,即使错误的数据是由另一个子程序生成的。这个想法是把可能的错误影响控制在一个有限的范围内。断言可以有效保证数据的正确性,防止整个程序因为脏数据而运行在不稳定状态。断言的使用方法参考《代码大全2》中的“防御性编程”一章。这里有一个简短的摘录来总结它的总体思路:1.使用错误处理代码来处理预期的情况,并使用断言来处理不应该发生的情况。2.避免将需要执行的代码放在断言中3.使用断言来注解和验证前置条件和后置条件4.对于高健壮的代码,应该在处理错误之前先使用断言5.为了内部系统的可靠性使用自己的断言数据,而不是外部不可靠数据,您应该使用错误处理代码。在iOS编程中,我们可以使用NSAssert来处理断言。例如:-(void)printMyName:(NSString*)myName{NSAssert(myName==nil,@"Thenamecannotbeempty!");NSLog(@"Mynameis%@.",myName);}我们验证myName属性的安全性,需要保证不能为空。NSAssert会检查其内部表达式的值,如果为假,则继续执行程序,如果不为假,则让程序崩溃。每个线程都有自己的断言捕获器(NSAssertionHanlder的一个实例)。当断言发生时,捕手会打印断言信息和当前类名、方法名等信息。然后抛出NSInternalInconsistencyException异常让整个程序崩溃。并在当前线程的断言捕获器中执行handleFailureInMethod:object:file:lineNumber:description:输出上述信息。当时发布程序的时候断言不能带入安装包。您不希望程序在用户的机器上崩溃。在项目设置中可以设置开启和关闭断言:release版本设置NS_BLOCK_ASSERTIONS后断言无效。尽量不要使用Try-Catch。并不是说Try-Catch等异常处理机制不好。相反,很多人在编程中错误地使用了Try-Catch,在核心逻辑中使用了异常处理机制。将其用作GOTO的变体。在Catch里面写了很多逻辑。委婉地说,在这种情况下为什么不使用ifelse。事实上,异常处理只是用户在软件中处理异常的一种情况。一种常见的情况是子程序抛出一个错误,让上层调用者知道子程序发生了错误,让调用者使用合适的策略来处理异常。一般情况下,异常处理策略是Crash,让程序死掉,打印出堆栈信息。在iOS编程中,抛出错误的方式往往更为直接。如果上层需要知道错误信息,一半会传入一个NSError指针的指针:-(void)doSomething:(NSError*__autoreleasing*)error{...if(error!=NULL){*错误=[NSErrornew];}....}能留给异常处理的场景已经很少了,所以在iOS编程中尽量不要使用Try-Catch。(PS:我看过用Try-Catch防止程序崩溃的设计,如果不是被逼的,尽量不要使用这个策略。)尽量把不崩溃的BUG弄出来,让它崩溃。上面主要讲了如何知道“死机”BUG,“BUG”的合理制造,还有一种就是尝试去死机还没有死机的“BUG”,没有更靠谱的方法这个还是靠暴力吧,比如写一些数组越界之类的,比如那些多线程难调的bug,想办法让它崩溃,这样会更方便crash的时候再找,简单来说就是抱着让程序“死”的心态去编程,活到死。需要你去发现它,它就在那里,只增不减。所有的bug都找你,你很少主动找bug。程序死了,然后我们还要加班。其实,whatwearelookingforisthecauseoftheBUG.找出bug的罪魁祸首,比较理论的一点是:在一堆可能的原因中ons,找出那些和BUG有因果关系的原因(注意是因果关系,不是相关关系)。因此,解决BUG一般可以分两步进行:合理假设,找到概率最大的一系列原因。分析以上发现的原因与BUG的因果关系。必须确定这个BUG是某种原因导致的,而且只是改了原因导致的。即确定具体原因是BUG的充分必要条件。找到原因后,剩下的事情就比较简单了,改代码就可以解决了。理性假设其实BUG产生的原因可以分为两类:我们自己程序的问题。系统环境,包括OS、libraries、frameworks等。前者找到了,我们就可以改变它。后者比较无能为力,要么投诉,要么发邮件给开发者,最后能不能改就不知道了。比如iOS做framework的时候,category会报找不到方法的异常,到现在也没有解决。当然,一般情况下,99.999999%的程序问题的原因都是我们自己造成的。所以理性假设是第一位的:首先怀疑你自己和你自己的程序,其次才是其他一切。程序的问题其实是开发者自己的问题。BUG毕竟是程序员的亲子,BUG是我们一手造成的。开发者会产生bug的原因大致有以下三种:知识储备不足,比如iOS中常见的空指针问题,经常被发现是由于不熟悉iOS的内存管理模型导致的。误解,典型的就是数组越界错误。而且类型转换的时候没注意。比如下面这个程序://array.count=9for(inti=100;array.count-(unsignedint)i>10;){i++.....}按理来说这应该是一个可以被正常执行。但是如果你运行它是一个无限循环。可能是死循环的问题,你修改了很多天也没有解决。直到我同事告诉你,array.count返回的是NSUInterge,当它和unsignedinteger一起使用时,如果有负值就越界了。你突然意识到:该死,这是类型问题。逻辑错误这是思路,但也是最严重的问题。一旦发生,就很难发现。人总是最难怀疑自己的思维方式。比如死循环的问题,最严重的就是函数之间的循环引用,还有多线程的问题。但幸运的是,绝大多数的BUG都是由于知识储备不足和大意造成的。因此,合理假设的第二条规则:首先怀疑自己的知识储备和粗心等人为因素等基本原因,并利用这些原因来寻找具体问题。然后怀疑棘手的逻辑错误。有了以上合理怀疑的一些基本攻略,一些基础材料是不能少的。这是崩溃的常见原因。最后还是要落地到这些具体的原因或者代码上,而是找到与BUG的因果关系。一个已经释放的对象被访问过,比如NSObject*aObj=[[NSObjectalloc]init];[aObjrelease];NSLog(@"%@",aObj);访问类数组对象越界或插入不存在的null对象字节对齐的方法,(类型转换错误)栈溢出多线程并发操作重复NSTimer合理性假设3:尽可能多地找出可能的具体原因可能的。因果分析首先要说明,我们要找的是“因果关系”,而不是“相关性”。这是两个极其混乱的概念。此外,很多时候我们将相关性误认为因果关系。比如在解决一个多线程问题的时候,发现了一个数据混乱的问题,但是一直想不通。终于,有一天你不小心给一个对象加了锁,数据就正常了。那你说这个问题是对象没有枷锁造成的。但是根据你上面的分析,只能断定对象是否被锁定与数据异常有关,而不是数据异常的原因。因为你没能证明对象加锁是数据异常的充分必要条件,只是用了单因变量实验,变量是锁链状态,取值x=[0,1],x是整数.那么实验结果就是有无枷锁与数据异常正相关。相关性:在概率论和统计学中,相关性(Correlation,或相关系数或关联系数)表示两个随机变量之间线性关系的强度和方向。在统计学中,相关性是衡量两个变量相对于它们彼此独立的程度。在这个广义的定义下,根据数据的特征定义了很多系数来衡量数据的相关性。因果关系:因果关系是一个事件(即“原因”)与第二个事件(即“结果”)之间的关系,其中后一事件被认为是前一事件的结果。错误地将相关性等同于因果关系。不仅是程序员,几乎每个人都会犯的常见逻辑错误。为了加深你的理解,你可以看看这个小科普:相关≠因果关系。因果分析的首要问题是不要被自己的逻辑错误所蒙骗,要正确区分相关和因果关系。不要将相关性等同于因果关系。然后就是因果分析的内容。前面反复说过,因果分析的目的是确定一个特定的原因是BUG发生的充分必要条件。那么判断这件事情,需要两个步骤:充分性证明必要性证明关于充分性证明,这基本上是正常的逻辑推理。基本思路是能够还原BUG发生的路径,从原因到BUG发生的代码,采取了什么样的函数调用和控制逻辑。确认了这一点,基本就证明足够了。一般情况下,根据Crash的堆栈信息,可以很直接的证明充分性。关于必要性证明,这个比较难。充分性和必要性的定义如下:当“ifAthenB”命题为真时,A称为B的充分条件,B称为A的必要条件。那么必然性就是BUG可以是BUG的成因。这个说法有点啰嗦。也就是说,你要确认这个BUG是可以解释原因的,这个BUG是而且只能是这个原因造成的。只有证明了足够的必要性,才能真正找到BUG的原因。参考:iOS开发中断言的使用——NSAssert()iOS常见Crash及解决方法相关≠因果关系原文:http://blog.jobbole.com/68678/
