DOTNET世界中的异常机制

------------------------------ 

Exception是很基础,可是后期很麻烦的东西。初学者会认为exception仅仅是:

try
{
}
catch(Exception ex)
{
}
finally
{
}

 

 

但是到了后期开发,问题变得复杂。当代码积累到达一定的量(20%使用了.net framework,80%都是自己开发的dll)。那么当我们调用一个method的时候,心里会不清楚是否有存在的危险,结果导致try catch满天飞,却依然抛出了不知哪里的exception。

 

在Java世界里面,有个checked exception机制;编译器显示的告诉用户当前方法存在的exception,让用户处理,或者再标注throw关键词在method,例如:

public void method throw Exception
{
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点中的一点。例如我们的

        [STAThread]
        
static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form2());
        }

相关文章: