异常(Exception),简单来说就是我们没有想到的东西,一个异常被抛出来,证明一件我们意想不到的事情发生了。如果在测试环境,这通常表明又一个bug出现了;如果在生产环境,这通常意味着:系统又出了一次错误,客户即将着手准备投诉,麻烦就要到来。异常导致暴风雨,这可不是蝴蝶效应。
异常的出现是没有办法完全避免的,完全避免也是没有意义的。下面我们主要探讨异常出现后,如何才能迅速知道异常出现的原因,从而能够高效率地对问题进行修正。
在我们平时的开发中,异常通常会被记录下来作为错误日志,以供错误排查修正时使用。在记录异常信息的时候,一般会统一记录当前时间,异常的消息,异常的跟踪堆栈信息。如下:
public void TestException()
{
try
{
//干活
}
catch (Exception ex)
{
LogError(ex);
}
}
public static void LogError(Exception ex)
{
Save(DateTime.Now, ex.Message, ex.StackTrace);
}
在这种处理方式下,一旦出现异常,我们记录下了异常的几个相关项:
l 什么异常(What),通过Message来反映,如果加上异常的类型名称(typeof(Exception).FullName)就更好了,目前我们好像很少记录这个;
l 什么时候发生的(When),通过Now来反映;
l 哪里发生的(Where),通过StackTrace来反映。
但日后我们根据记录下来的这些异常信息来推断当初为什么会发生这些异常还是比较困难的,因为它没有将在什么条件下,为什么会发生异常(Why)记录下来。
在之前我开发的那个Webwalker(人生杯洗具)中,爬虫会爬过无数页面,将它们的内容抓取下来,然后分析出信息。这个过程中是很有可能出现异常。最常见的异常有两种:
l WebException:公司的网络时断时续,有时候下载网页内容失败,这个时候记录下来的异常信息类似:
Connection to remote server failed./Connection Timeout
在****行代码
l IndexOutOfRange:在文本匹配出错的时候出现,记录下的信息类似:
IndexOutOfRange,Index must >= 0 and < capacity之类
在****行代码
异常是记录下来了,但对于我来说,这些异常信息对我分析异常为什么出现没有任何帮助,因为有些页面不会出错,有些却会,对于每个页面,一旦出错,它就记录一条一模一样的错误信息。我根据异常信息没法找到具体是哪些页面出现了错误。为了找到具体是哪些页面出现了问题,我只能够对已经访问过的url列表一个个进行测试,然后期待代码在异常的断点处变黄。
想想我们平时在开发或者维护过程中的行为,是不是跟这个很类似?
测试部的同事发现系统出了问题,如果Ta不能协助提供出错前的操作步骤(访问了哪个页面,进行了步骤1、2、3,输入了文本A、B、C,还要提供截图),我们在寻找bug的时候会多么痛苦(提供了也痛苦,虽然轻微点),甚至某些问题根本就是不可重现的灵异事件!
如果问题发生在维护阶段,我们这个时候就会前所未有的紧密联系客户,询问Ta在访问哪个url,进行什么操作的时候出错。客户毕竟没有测试部专业,不会每次操作前先截图,所以无疑我们debug更加痛苦,被客户鄙视得体无完肤,耗费的工作量就甭提啦。
我们虽然有错误记录的机制,但记录下来的错误信息却不能对我们的工作起到太大的支持作用。
为了改变这种状况,我们需要将异常出现的条件(Why)也记录到错误信息中,这样我们回头看错误信息的时候,一下子就对出现异常的条件清楚明了:访问abcd.aspx并执行步骤9、5、2、7的时候出了异常!这样我们一下子就定位到了问题,重现问题和排错就简单了。
那具体怎么做呢?我们需要对代码抛出来的异常包装一下,在异常里面加上有我们业务逻辑的东西,或者说是异常出现的上下文信息。
我在Webwalker里面是这样做的,创建了一个新的异常类,这个异常类会提供Url信息。这样我一下子就能知道是操作什么Url的时候出错了。代码如下:
public class SpiderException : Exception
{
public SpiderException(string url, Exception ex)
: base(string.Format("Url:{0}\r\nInnerMessage:{1}", url, ex.Message), ex)
{
Url = url;
}
public string Url { get; private set; }
public override string StackTrace
{
get
{
return this.InnerException.StackTrace;
}
}
}
在使用的时候,将代码稍为改变了一下:
try
{
//我爬啊爬!
}
catch (Exception ex)
{
LogError(new SpiderException(url, ex));
}
这样我就将上下文的url信息加到了Message中,错误记录中也会记录下异常出现的条件和原因了。现在我只需要一看错误信息就知道是爬哪个页面的时候出错了,不再需要自己苦苦查找。