DOTNET世界中的异常机制
------------------------------
Exception是很基础,可是后期很麻烦的东西。初学者会认为exception仅仅是:
{
}
catch(Exception ex)
{
}
finally
{
}
但是到了后期开发,问题变得复杂。当代码积累到达一定的量(20%使用了.net framework,80%都是自己开发的dll)。那么当我们调用一个method的时候,心里会不清楚是否有存在的危险,结果导致try catch满天飞,却依然抛出了不知哪里的exception。
在Java世界里面,有个checked exception机制;编译器显示的告诉用户当前方法存在的exception,让用户处理,或者再标注throw关键词在method,例如:
{
xxxxx
}
这个看似美妙的设计,在DOTNET里面却被抛弃了,根据dotnet设计者的原话:The Trouble with Checked Exceptions
不引入java的checked exception的原因在于:
1. 版本问题。例如Hello()在version 1里面,抛出ABC三个异常,但是到了version 2里面,会再抛出D。这样就导致了原始调用Hello()的代码失效了。
2. 扩展性问题。假设10个方法每个抛出10个异常,那么当一个高级方法调用这10个方法,就需要处理100个异常了。
这样导致了在代码里面用户简单的throw,没有达到checked exception的实际目的。
虽然到了Enterprise Library里面,出现了 Exception Handling Application Block,可是在我看来,只是把问题“工程化” 而已,并没有从技术角度实际解决exception的问题。例如在通过外部的配置文件,控制当前exception是否应该抛出等。(题外话,这种处理我个人认为不会成熟;因为只要把业务代码分散在多于1个地方,维护难度就指数增加。)
我花了几个小时翻遍了codeproject,里面没有一个人提出成熟的可用的代码(毕竟是dotnet天生不支持。。) ,不过还是有借鉴的文章,例如:Simplifying Exception-Safe Code 提出了一种exception业务回滚的思路。
既然前人没有成果,我只好自己制造了。
Exception的分析
-------------------------------
首先,我们的代码是什么? 或者说,计算机在干什么?
就是给出指定的输入,代码返回符合期望的输出。这个是计算机的基本功能。那么异常就是出现了和上面定义不同的情况。
那么什么时候会出现问题?异常只有在什么时候会被跑出来?
1. 没有给定符合要求的输入。
2. 方法内部由于外部因素(磁盘IO、网络、系统内存等软硬分割区域),产生了超乎预期的操作。
3. 方法内部运算结果没有符合预期的输出结果(例如没有找到)
和
4. 代码存在的bug、缺陷,导致方法无法获取期望的输出。 (这是个非常恶心的东西)
根据这4点,exception就分为了 预期的异常expectedException 和意外的异常unexpectedException.
预期的异常,仅仅用于程序流程控制,而不需要维护、即不需要最终展示给维护人员。因此处理过程中仅仅需要记录、拦截、抛出。
意外的异常,一定会被抛出来,需要详细记录。这个是需要仔细研究,修复bug的。
预期的异常 ExpectedException
--------------------------------------
预期的异常包括了:
1. 没有给定符合要求的输入。(VerificationFailedException )
2. 方法内部由于外部因素(磁盘IO、网络、系统内存等软硬分割区域),产生了超乎预期的操作。 (UnexpectedExternalException )
3. 方法内部运算结果没有符合预期的输出结果(例如没有找到) (UnhandledResultException)
由于exception是可控制的,我们也知道什么地方会抛出异常,抛出了如何处理。 因此设计exception是否应该抛出的时候,就得到:
1. 当前方法是否允许抛出异常。
2. 当前方法的返回值能否代表异常。
有些方法,是不允许抛出异常的,特别是void的方法,我们并没有期望他们能够一定成功,所以这种方法不需要设计exception,直接try catch就结束了。
有些方法,返回值能代表异常,例如:获取当前温度,如果返回-1,表示获取失败。等等。这种情况下,这种方法也不需要抛出异常,用try catch全部包住。
那么,剩下的方法,就是会抛出异常的了。
这里就来到了一个有趣的逻辑点:由于系统都是由无数的方法嵌套调用而成的,那么最坏情况下是最顶端的方法不抛出异常,也就符合了上面提到的2点中的一点。例如我们的
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form2());
}