.NET异常编程模型漫谈
此篇博文只是对.NET异常编程模型随便聊聊不会系统性的讲。异常编程模型这个名字叫的较屌,但是这个编程模型却很亲民,.NET异常编程模型就是在callee(被调用方法)中抛出异常,在caller(调用方法的方法)中捕获异常。从C语言到Java,再到C#异常模型比较统一,大家也经常去写try...catch...。
ApplicationException(AE)和SystemException(SE)
最初.NET团队设计AE和SE的目的是为了区分应用程序异常和BCL异常,继承自AE的自定义异常在应用程序代码中抛出,继承自SE的异常在BCL的代码段中抛出。
这样可以友好的将异常分为两类,并且根据其基类就可以判断这个异常在哪里抛出,但是目前有这样的两个问题。
1.ArgumentException等继承自SE,但是ArgumentException是一个很通用的异常类,真心没有必要再在应用程序中定义一遍。
2.TargetInvocationException 是定义在BCL中的类,但是他继承自AE。
从1和2看目前将异常分为AE和SE两类的原则就不再适用了。当然BCL中提供的AE和SE不适用也没关系,如果真的有必要区分应用程序异常和.NET BCL异常的话我们可以自定义一个异常基类。让所有的应用程序自定义异常都去继承这个基类。但是真的有必要吗?我们在caller捕获的异常大多会预测到或者是大多是由callee在注释中提供。我们想去特殊处理哪种异常都在代码中捕获,不想做特殊处理的异常则直接用Exception捕获即可。这样看来在注释中出现我们自定义的异常基类就会显的很鸡肋,用张全蛋的一句话来说,其实这样定义一个异常基类并没什么卵用。
吐槽一下.NET异常编程模型的设计
C#.NET出现的比java晚一些,并且有参考java做设计,从我的角度来说,我并不完全满意.NET团队提供异常模型,首先是BCL中提供的AE和SE,这样做只会加深了异常的依赖深度。其次,与java的异常编程模型相比,我更喜欢Java的异常编程模型。在java中要么就向上层继续抛出异常,在方法签名中写明,要么捕获异常,如果不这样做就会出现编译错误,而在C#中开发人员可以忽略callee中抛出的异常,我认为在代码中捕获所有异常,写异常处理逻辑是非常必要的。
善用异常编程模型原则
异常编程模型实现了编程语言的错误报告机制,callee主动抛出异常,捕获异常caller捕获异常,中间可能会有几次甚至几十次调用发生,但是中间的caller并没有异常传播代码。所以我们应该善用异常编程模型去构建健壮,易于维护的应用程序。以下是我的对使用异常编程模型的六个建议
- 在代码主动抛出异常标识处理失败,而不是通过返回Bool值或者ref out等传入的参数标识处理成功失败
- 熟悉目前.NET平台BCL提供的异常类。
- 在BCL中找不到使用的异常类,则在应用程序中添加自定义异常,个人建议定义在Model中,便于异常的传播。
- 若非业务需要则统一捕获异常,一般都会在原有简单的业务的复杂度有一定的成长性,原本业务逻辑代码就够多的了,异常处理逻辑就不要添乱了,把异常处理逻辑拿出来放在Facade层。可以实现业务逻辑和异常处理逻辑解耦的目的。
- 在方法头部注释中标识出会抛出的异常,若caller未对callee捕获异常也要将callee中的异常注释复制到caller的头部的注释中。
- 自定义异常直接从Exception继承就好,完全不用鸟ApplicationException。