我这个星期经历了一次艰苦的调试过程。到今儿晚上总算把错误找出来,唉,辛苦。 :-( 下面我把这一周的调试经验整理一下,贴出来,希望对有类似问题的兄弟姐妹们有帮助。
开发环境:
中文Windows 2000 server + ASP.net 2003
具体地说,是用IBM R50e笔记本(详细配置: ) , 在中文Windows 2000 server上用 Visual Studio.net 2003 中文professional版 开发Asp.net Web程序。
过程陈述:
这个系统开发很顺利,从开始写代码到调试到交付用户,只用了不到2个月的时间。交付用户后,初期反映不错,但是过了一两个星期后,用户的反馈来了,说速度明显变慢。我登录系统试了一下,确实如此,有时打开一个简单的页面甚至要花半分钟。怎么办呢,当然是把源代码拿来调试跟踪啰。刚开始进展顺利,花了一个上午的时间就找到了使系统变慢的“罪魁”:用debug跟踪,发现当用F10(单步执行)运行一个名为ReadCodeTable函数时,前面几次单步过得很快,但到后面就明显慢了。糟糕,这个函数肯定有问题,并且很可能有内存泄露。于是,我把这个函数的源码找到,仔细阅读,然后跟踪,最终代码归结到下面一小段:
for(int i=0; i<2000; i++)
{
SqlConnection connection = null;
try
{
connection = new SqlConnection("Integrated Security=True;Data Source='chen';Initial Catalog='HHAfis';");
connection.Open();
}
catch(Exception e1)
{
throw new Exception(e1.Message + "i=" + i.ToString());
}
finally
{
connection.Close();
}
}
中间的具体执行的函数在此略过,因为确实与这个bug无关。当然,我承认程序里用这段代码并不高明,不过,这不是问题的要害。当我运行了几次这个代码段之后,Web页面报异常错误信息:“连接超时,可能原因是连接池满...”(大意如此)。我极为惊讶,不会呀,我在finally里把connection给关闭了呀。于是我把这段代码拷贝出来,(为了更快复现错误,我把循环次数改为2000),放到NUnit中测试,结果一点问题也没有。但是只要放到该项目的任何地方,只要运行这段代码,总会出现运行缓慢的问题,多运行几次,总会报前述错误信息。看样子,connection确实没有关闭,但为什么在NUnit可以通过?
后来我想是不是NUnit是WinForm程序,所以可以通过,但是我启动了一个新的最基本的WebForm程序,也可以通过。看来这段代码本身没问题,问题出现在这段代码所处的上下文中。因此,我就开始了在程序中寻找bug的将近一周的痛苦历程。
整个一个项目,上百个文件,要找出与这段代码相关的可能是bug原因的代码,怎么办?我采取了逐步递减法。也就是把文件逐个递减,每递减一个就重新测试这段代码,假如递减之后,问题就不再出现,那么,这个递减的文件就有嫌疑哟。可惜,一直递减到只剩下这一个文件,bug依然存在。看样子bug就是在这个文件本身了。现在,继续用递减法。把文件的其它函数全部注释掉,只留下这一个函数,bug还在不在? 在~~~~,老大。(累死我了,臭bug,你还在!)。再把这一个函数的其它部分通通注释,就剩这段代码,bug还在不在? 当然在,要不我就不用写这篇文章了。
这就怪了,代码本身已经证实没问题,所有环境已经全部清理了,怎么bug还没揪出?嗯,将Web.config, global.asax, 等配置文件全部换掉,可是,bug老大哥它还在!我倒!
百试百败、百感交集、百无聊赖之际,真有山重水复之感。突然,灵光一现,呵呵,有柳树在前方出现了。我突然想起来,过去系统装过Visual Source Safe 6.0c,曾经用这个管理过源代码,后来感觉不方便,就把VSS卸了,但是VSS管理过的项目文件依然在用,这不,每次进入vs.net,都提示:“这个项目受源代码管理器管理,但现在找不到源代码管理器”云云,是不是跟这有关?当下不敢怠慢,立马尝试,把每个项目都移出解决方案,再重新建新项目,添加到解决方案中。OK!Bug不见了!唉,这真是个幸福的时刻。只有经历了臭虫的折磨,才知道抓住臭虫的幸福,这真是我今天最深的体会了。
幸福嘛,当然要与大家共享了,下面是我的几点体会。
经验总结:
1、可能很多人跟我一样,有些多余的东西总是舍不得花时间将他们清理,结果留下了种种隐患。就拿我这个案例来说吧,假如我最初卸载VSS时,同时把vs.net中的遗留信息清除干净,就不会让这个小臭虫吸我这么多血了(一周的工作时间啊)。
2、我曾经武断地认为,错误就出在上面的代码上,但实际上上面的代码只是一个暴露问题的代码,而不是问题所在的代码,而这两者的区别是很大的。一般来说,暴露错误代码可以用来测试bug是否还存在,因此,在没有找出问题的真正原因之前,决不要写代码使问题不出现了事。要知道,掩盖一个错误现象是很容易的,但是这个错误总有一天会在另一个地方冒出来,那时,是你继续头痛的时候了,并且,那时的臭虫可能比现在的这个吸掉你更多的血哟。对于臭虫来说,我们最好不要当献血英雄,对吧?:-) 就到这儿,就到这儿吧,一休哥~~~~