如何合理地制造“BUG”并且查找BUG

什么是BUG,简单点说就是,程序没有按照我们预想的方式运行。我比较喜欢把BUG分成两类:

1.Crash掉的

2.没有Crash掉的

可能在平时的编程实践中,往往简单的把BUG与Crash基本等价了。而且我们很多精力也都放在解决Crash的Bug上面。而对于没有Crash掉的BUG,似乎没有过多的关注。但是,实际情况上那些让人痛彻心扉的“天坑”往往是那些没有Crash掉的BUG造成的,比如前一段时间OpenSSL心脏大出血。为什么这么说呢?且听我慢慢道来。

如何合理的制造BUG

Crash掉的BUG,用程序的死证明了你的程序存在问题,你必须抓紧时间来解决程序的问题了。而没有Crash掉的Bug,像是一个善于撒谎的人,伪装成可以正常运转的样子,让整个程序运行在一个不稳定的状态下。虽然外表看起来好好地(没有crash),但是里子早就烂透了,一旦报露出问题往往是致命的,比如OpenSSL的心脏大出血。这就是前人总结的“死程序不说谎”。

Crash不可怕,可怕的是程序没有Crash而是运行在一个不稳定的状态下,如果程序还操作了数据,那带来的危害将是灾难性的。

所以放心的让程序Crash掉吧,因为当他Crash掉的时候,你还有机会去修正自己的错误。如果没有Crash,那就有可能要给整个程序和产品收尸了。因此合理制造“BUG”的原则之一,也是最大的原则就是:尽量制造Crash的BUG,减少没有Crash的BUG,如果有可能将没有Crash掉的Bug转换成Crash的BUG以方便查找。

NSAssert

这个应该都比较熟悉,他的名字叫做“断言”。断言(assertion)是指在开发期间使用的、让程序在运行时进行自检的代码(通常是一个子程序或宏)。断言为真,则表明程序运行正常,而断言为假,则意味着它已经在代码中发现了意料之外的错误。断言对于大型的复杂程序或可靠性要求极高的程序来说尤其有用。而当断言为假的时候,几乎所有的系统的处理策略都是,让程序死掉,即Crash掉。方便你知道,程序出现了问题。

断言其实是“防御式编程”的常用的手段。防御式编程的主要思想是:子程序应该不因传入错误数据而被破坏,哪怕是由其他子程序产生的错误数据。这种思想是将可能出现的错误造成的影响控制在有限的范围内。断言能够有效的保证数据的正确性,防止因为脏数据让整个程序运行在不稳定的状态下面。

关于如何使用断言,还是参考《代码大全2》中“防御式编程”一章。这里简单的做了一点摘录,概括其大意:

**1. 用错误处理代码来处理预期会发生的状况,用断言来处理绝不应该发生的状况。

  1. 避免把需要执行的代码放到断言中
  2. 用断言来注解并验证前条件和后条件
  3. 对于高健壮性的代码,应该先使用断言再处理错误
  4. 对来源于内部系统的可靠的数据使用断言,而不要对外部不可靠的数据使用断言,对于外部不可靠数据,应该使用错误处理代码。**
    而在IOS编程中,我们可以使用NSAssert来处理断言。比如:

*- (void)printMyName:(NSString )myName
{

NSAssert(myName == nil, @"名字不能为空!");
NSLog(@"My name is %@.",myName);  

}**
我们验证myName的安全性,需要保证其不能为空。NSAssert会检查其内部的表达式的值,如果为假则继续执行程序,如果不为假让程序Crash掉。

每一个线程都有它自己的断言捕获器(一个NSAssertionHanlder的实例),当断言发生时,捕获器会打印断言信息和当前的类名、方法名等信息。然后抛出一个NSInternalInconsistencyException异常让整个程序Crash掉。并且在当前线程的断言捕获器中执行handleFailureInMethod:object:file:lineNumber:description:以上述信息为输出。

当时,当程序发布的时候,不能把断言带入安装包,你不想让程序在用户机器上Crash掉吧。打开和关闭断言可以在项目设置中设置:

在release版本中设置了NS_BLOCK_ASSERTIONS之后断言失效。

尽可能不要用Try-Catch

并不是说Try-Catch这样的异常处理机制不好。而是,很多人在编程中,错误了使用了Try-Catch,把异常处理机制用在了核心逻辑中。把其当成了一个变种的GOTO使用。把大量的逻辑写在了Catch中。弱弱的说一句,这种情况干嘛不用ifelse呢。

而实际情况是,异常处理只是用户处理软件中出现异常的情况。常用的情况是子程序抛出错误,让上层调用者知道,子程序发生了错误,并让调用者使用合适的策略来处理异常。一般情况下,对于异常的处理策略就是Crash,让程序死掉,并且打印出堆栈信息。

而在IOS编程中,抛出错误的方式,往往采用更直接的方式。如果上层需要知道错误信息,一半会传入一个NSError的指针的指针:

- (void) doSomething:(NSError* __autoreleasing*)error
{
    ...
    if(error != NULL)
    {
        *error = [NSError new];
    }
    ....
}

而能够留给异常处理的场景就极少了,所以在IOS编程中尽量不要使用Try-Catch。

(PS:见到过使用Try-Catch来防止程序Crash的设计,如果不是迫不得已,尽量不要使用这种策略)

尽量将没有Crash掉的BUG,让它Crash掉

上面主要讲的是怎么知道Crash的“BUG”。对于合理的制造“BUG”还有一条就是尽量把没有Crash掉的“BUG”,让他Crash掉。这个没有比较靠谱的方法,靠暴力吧。比如写一些数组越界在里面之类的。比如那些难调的多线程BUG,想办法让他Crash掉吧,crash掉查找起来就比较方便了。

总之,就是抱着让程序“死掉”的心态去编程,向死而生。

如何查找BUG

其实查找BUG这个说法,有点不太靠谱。因为BUG从来都不需要你去找,他就在那里,只增不减。都是BUG来找你,你很少主动去找BUG。程序死了,然后我们就得加班加点。其实我们找的是发生BUG的原因。找到引发BUG的罪魁祸首。说的比较理论化一点就是:在一堆可能的原因中,找到那些与BUG有因果性的原因(注意,是因果性,不是相关性)。

于是解决BUG一般可以分两步进行:

合理性假设,找到可能性最高的一系列原因。

对上面找到的原因与BUG之间的因果性进行分析。必须确定,这个BUG是由某个原因引起的,而且只由改原因引起。即确定特定原因是BUG的充分必要条件。

找到原因之后,剩下的事情就比较简单了,改代码解决掉。

合理性假设

其实,BUG发生的原因可以分成两类:

1.我们自己程序的问题。

2.系统环境,包括OS、库、框架等的问题。

前者找到了,我们可以改。后者就比较无能为力了,要么发发牢骚,要么email开发商,最后能不能被改掉就不得而知了。比如IOS制作framework的时候,category会报方法无法找的异常,到现在都没有解决掉。

当然,一般情况下导致程序出问题的原因的99.999999%都是我们自己造成的。所以合理性假设第一条:

首先怀疑自己和自己的程序,其次怀疑一切。
而程序的问题,其实就是开发者自己的问题。毕竟BUG是程序员的亲子亲孙,我们一手创造了BUG。而之所以能够创造BUG,开发者的原因大致有三:

1.知识储备不足,比如IOS常见的空指针问题,发现很多时候就是因为对于IOS的内存管理模型不熟悉导致。

2.错心大意,比较典型的就是数组越界错误。还有在类型转化的时候没注意。比如下面这个程序:

**//array.count = 9
for (int i = 100; array.count - (unsigned int)i > 10 ; )
{

i++
.....

}**
按道理讲,这应该是个可以正常执行的程序,但是你运行的话是个死循环。可能死循环的问题,你改了很多天也没解决。直到同事和你说array.count返回的是NSUInterge,当与无符号整形相间的时候,如果出现负值是回越界的啊。你才恍然大悟:靠,类型的问题。

逻辑错误
这个就是思维方式的问题,但是也是问题最严重的。一旦发生,很难查找。人总是最难怀疑自己的思维方式。比如死循环的问题,最严重的是函数间的循环引用,还有多线程的问题。

但是庆幸的是绝大多数的BUG都是由于知识储备不足和粗心大意造成的。所以合理性假设的第二条:

首先怀疑基础性的原因,比如自己知识储备和粗心大意等人为因素,通过这些原因查找具体的问题。之后再去怀疑难处理的逻辑错误。
有了上面的合理性怀疑的一些基本策略,也不能缺少一些基本的素材啊。就是常见的Crash原因,最后我们还是得落地到这些具体的原因或者代码上,却找与BUG的因果性联系。

1.访问了一个已经被释放的对象,比如

NSObject * aObj = [[NSObject alloc] init];
 [aObj release];
 NSLog(@"%@", aObj);

2.访问数组类对象越界或插入了空对象

3.访问了不存在的方法

4.字节对齐,(类型转换错误)

5.堆栈溢出

6.多线程并发操作

7.Repeating NSTimer

合理性假设第三条:尽可能的查找就有可能性的具体原因。

因果性分析

首先必须先说明的是,我们要找的是“因果性”而不是“相关性“。这是两个极度被混淆的概念。而且,很多时候我们错误的把相关性当成了因果性。比如,在解决一个多线程问题的时候,发现了一个数据混乱的问题,但是百思不得其解。终于,有一天你意外的给某个对象加了个锁,数据就正常了。然后你就说这个问题是这个对象没有枷锁导致的。

但是,根据上述你的分析,只能够得出该对象枷锁与否与数据异常有关系,而不能得出就是数据异常的原因。因为你没能证明对象加锁是数据异常的充分必要条件,而只是使用了一个单因变量实验,变量是枷锁状态,取值x=[0,1],x为整形。然后实验结果是枷锁与否与数据异常呈现正相关性。

相关性:在概率论和统计学中,相关(Correlation,或称相关系数或关联系数),显示两个随机变量之间线性关系的强度和方向。在统计学中,相关的意义是用来衡量两个变量相对于其相互独立的距离。在这个广义的定义下,有许多根据数据特点而定义的用来衡量数据相关的系数。  

因果性:因果是一个事件(即“因”)和第二个事件(即“果”)之间的关系,其中后一事件被认为是前一事件的结果。
错误的把相关性等价于因果性。不止是程序员,几乎所有人常见的逻辑错误。为了加深认识,可以看一下这篇小科普:相关性 ≠ 因果性。

因果性分析的首要问题就是,别被自己的逻辑错误欺骗,正确的分辨出相关性和因果性之间的区别。不要把相关性等价于因果性。

之后便是因果性分析的内容了,之前一直反复说,因果性分析的目的就是确定特定原因是BUG发生的充分必要条件。那么确定这个事情,就需要两步:

1.充分性证明

2.必要性证明

关于充分性证明,这个基本上就是正常的逻辑推理。基本思路就是,能够还原出BUG出现的路径,从原因到BUG发生处的代码,走了怎样的函数调用和控制逻辑。确定了这个基本上就能够证明充分性。一般情况下根据Crash的堆栈信息能够,非常直接的证明充分性。

关于必要性证明,这个就比较困难了。充分性和必要性的定义如下:当命题“若A则B”为真时,A称为B的充分条件,B称为A的必要条件。那么必要性就是,BUG能够作为导致BUG的原因的原因。这个说法比较拗口。换种说法,就是你得确认这个BUG能够解释原因,这个BUG就是而且只是这个原因造成的。

只有证明了充分必要性,才能算是真正找到了BUG的原因。

参考:

1.iOS开发中断言的使用—NSAssert()

2.iOS 常见 Crash 及解决方案

3.相关性 ≠ 因果性

时间: 2024-09-28 07:58:29

如何合理地制造“BUG”并且查找BUG的相关文章

java-什么是bug 怎么找bug

问题描述 什么是bug 怎么找bug 什么是bug 怎么找bug 我是个菜鸟 请大神来解答一下 解决方案 bug 就是系统漏洞 就像玩游戏的时候掉出地图外 就是卡入了bug 解决方案二: 你可以将bug理解成系统的一个小错误,只是这个错误对整个系统的运行没有太大的影响 解决方案三: bug是发生在意想之后的情况,通常是由于逻辑不够严谨或对环境不够了解造成的,找bug这个需要有丰富的经验,因为有的bug很难分辨出来

能存活 19 年的 bug 不是 bug —— 有感于微软宣布修复了一个存在了19年的安全漏洞

近日,各大网站,包括新浪.腾讯.网易.搜狐都报道了一则关于微软宣布修复了一个存在了19年的安全漏洞的新闻. 对于一个软件制造界外的人来说,这是一个大快人心的消息,就跟一个隐藏了19年的纳粹分子终于被抓住的新闻一样轰动.但以程序员为职业的我,听到这样一个 消息,却有一种非常不解.甚至是荒谬.搞笑的感觉.从软件生产的角度讲,如果一个bug能存活19年,那它还是个bug吗? 一.很多项目生命期不超过19年 我在很多国企开发过项目,这些项目几乎每过几年都会重新开发一回,老项目或者废弃.或者推倒重来,遇到

[MySQL Bug]bug#66301(Percona Bug#1035225)简析

----------- 先跑test case 1.创建测试表 CREATE TABLE t(        id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,        k INT,        c CHAR(1),        UNIQUE KEY(k)) ENGINE=InnoDB; 2.当顺序执行如下SQL时 SQL1–INSERT INTO t(k) VALUES (1), (2), (3) ON DUPLICATE KEY UPDATE c

精通css(9)bug和修复bug

浏览器bug和不一致的显示方式是大多数CSS开发人员面临的主要问题.本文就bug问题作一些学习. 1.bug来源于自己 如果你写的布局跟你想象的不太一致,不要以为这是浏览器bug,首先应该想象是不是自己的问题.要么手贱忘了写";"或者把单词拼错了,要么是自己对css理解还不够. 2.IE中的bug IE上的bug无疑是众多浏览器中最多的,这主要是它的显示引擎使用了layout(布局).这是许多IE/Win显示bug的根源,所以理解layout概念还是很重要的. 2.1何为layout

iOS8系统Bug_iOS8已知Bug整理与bug解决方法

1.App Store开启速度慢 升级之后,身边的很多朋友都反映App Store开启速度慢了很多,打开就显示一片空白,甚至有一部分人根本打不开.可是我们使用的应用大部分都要从App Store中下载,这也给给用户带来了不小的麻烦. App Store无法开启 解决方法并不难,打开设置-无线局域网-DNS,接着将DNS码改为8.8.8.8即可恢复正常.成功之后再将DNS码换回去就大功告成了,如 果您忘记保存原始的DNS码也没关系,只要在设置中选择重置网络就能自动恢复初始状态,只不过之前记录的Wi

一个经典的二分查找算法的Bug

1:     binarySearch([] a, key) {2:         low = 0;3:         high = a.length - 1;4: 5:         (low <= high) {6:             mid = (low + high) / 2;7:             midVal = a[mid];8:9:             (midVal < key)10:                 low = mid + 1;11: 

Java理论与实践:平衡测试,第1部分:不要仅编写测试,还要编写bug检测器

对于许多团队来说,单元测试现在是开发过程的一个主要部分:JUnit 之类 的框架可以进行无损测试,尽管我们并不喜欢它,宁愿为某些 代码编写某些 测 试.单元测试运行效率很低,只能测试单个代码片段,并且,一般情况下,测试 代码的重用性通常很也低 -- 昨天为组件 A 编写的测试不能很好地用于测试 组件 B(示例代码除外). 典型的单元测试场景 在发现 bug 时,要做的第一件事是什么?您可能只是想去修复它,但是,在 长时间的运行中,这不是一个最有效的方法.在许多开发部门中,处理 bug 的 过程如

WPF框架的内存泄漏BUG

用户在使用GIX4某模块的过程中,内存只见加不见减.我们怀疑出现了内存泄漏,所以我花了相当一 段时间来进行此问题的排查. 我使用Red Gate公司的产品ANTS Memory Profiler 5进行应用程序的内存进行监视.并在过程中修改 程序中出现的一些问题.但是最后留下一个不知道原因的引用,如图1: 图1 泄漏对象引用图 由图中可以看出,Application.Resources永久地引用了临时控件SelectionDataGrid.原因出在 DeferredAppResourceRefe

bug检查工具——FindBugs

         Findbugs是一个静态分析工具,它检查类或者JAR文件,将字节码与一组缺陷模式进行对比以发现可能的问题.Findbugs自带检测器,其中有60余种Bad practice,80余种Correctness,1种Internationalization,12种Malicious code vulnerability,27种Multithreadedcorrectness,23种Performance,43种Dodgy.   安装地址:    1.      在线安装 安装地址: