文档章节

谈谈Exception,什么时候抛出?什么时候接住?

lzw_me
 lzw_me
发布于 2014/04/30 10:10
字数 1938
阅读 47
收藏 0

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());
        }

复制代码

 

因此,到目前为止我的分析都是对的,而且在 预期的异常 这部分,已经在数学上实现了一个有限的集合。 所以处理就变得简单了:我们只需要处理两方面:

1. 封锁异常,不再抛出。这种情况,所有的exception都会被拦截并且日志记录下来。没有什么难度。

 

2. 抛出异常并传递。 

由于上文分析了,预期的异常只有三种,所以一个方法如果抛异常,也最多只有3个,这样在throw的传递过程中,我们就非常容易实现了filter功能。从而 消除了 开篇提到了checked exception的2个问题。

 

当当前方法会抛出异常的时候,我们要控制需要抛出哪些异常。如果异常来自自身就很简单,问题就是异常来自调用的子方法传递出来的。这个时候就需要策略。

 

2.1. verificationFailedException。任何会抛出异常的方法都会潜在拥有这个异常。这个方法是否会抛出输入验证异常,和子方法无关。即子方法的输入验证在这个方法一定都被默认的try catch了。结论是:异常不会传递。但是会转变为当前的输入验证异常。

 

例如

MethodA()

{

 MethodB();

如果methodB存在输入错误。那么methodA默认已经处理了。除非methodA也存在输入错误,那么这个校验就传递到了外界。而这个exception是从methodA抛出来的,和methodB无关。

 

2.2. UnexpectedExternalException 是否传递?一个方法

 MethodA()

{

Call1();//net io

Call2();//file io

Call3();//file io 

到目前为止,这种级别的异常,都会被传递出去,而不被记录。似乎没有反例。所以结论是:继续抛出,不被记录,直到不抛出的地方。

 

2.3. UnhandledResultException  本质来说,这种异常和输入错误异常是一致的。因为我调用的方法返回值,等价于外界传入一个参数。所以reduce to 2.1 的处理。

 

所以,一个方法可能抛出的异常中:

1. 来源于自己的验证。

3. 来源于自己的,如果是子类的,会规划了1的处理机制,即如果输入参数不符合会抛异常,就抛;否则就不抛,自己处理。

 

2. 来源于子类、自己,处理机制就是继续抛出,直到遇到不抛出的类。 

 


意外的异常 UnexpectedException  

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

一定来源于bug。也一定被抛出。处理机制在不抛出的类中。也以往的一致。一般这种exception发源地一定是外部的dll,不是自己的。所以也好控制。不涉及自己的代码维护。 

 


本文转载自:http://www.cnblogs.com/zc22/archive/2010/04/17/1714361.html

lzw_me
粉丝 4
博文 149
码字总数 42741
作品 0
昌平
程序员
私信 提问
多线程-02-线程的生命周期

线程的生命周期 新建 就绪 等待执行 运行 阻塞 死亡 线程被创建之后处于New状态,start()方法调用之后处于Runnable状态,至于什么时候开始则需要等待JVM的调度。 线程的run()是线程的执行体,...

FutaoSmile丶
2018/01/12
0
0
四netty学习之重连, ReaderTimeoutHandler, IdleStateHander

关于重连 什么时候需要重连呢?1. 启动的时候,没有成功连接2. 运行过程中,连接断掉 对第一种情况的解决方法:实现ChannelFutureListener用来监测是否连接成功,不成功的话重试 对第二种情况的解...

plugin
2015/11/05
150
0
java经典面试笔试题(扫盲贴)

1、什么是对象序列化,为什么要使用? 所谓对象序列化,就是把一个对象以二级制的方式保存到硬盘上,方便远程调用。 2、值传递与引用传递的区别? 值传递就是把一个对象的值传给一个新的变量...

Carbenson
2015/09/21
259
0
java,什么情况下用运行时异常?

什么情况下使用运行时异常?什么时候使用编译异常? 比如自己写的工具类方法,做强转用的方法: public static Conversion getConversion() { try { } } catch (Exception e) { throw new R...

jack_jones
2013/12/19
790
2
Effective Java 第三版——70. 对可恢复条件使用已检查异常,对编程错误使用运行时异常

Tips 书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code 注意,书中的有些代码里方法是基于Java 9 API中的,所以JDK 最好下载 JDK 9以上的版本。 70. 对可恢复条件...

M104
03/25
0
0

没有更多内容

加载失败,请刷新页面

加载更多

linux查询日志命令总结

【背景】 排查线上环境问题,少不了去线上查日志。而使用什么命令,能快速准确地查到我们需要查找地日志信息,也是我们需要掌握的一项技能。 【命令】 Linux查看命令有多种:tail,head,cat...

chen-chen-chen
28分钟前
7
0
net/http 接收文件

代码展示,如何使用golang 自带net/http,将Form表单中提交上来的文件,指定位置保存。 ReadHtmlFile OutHtml(html网页,表单测试代码使用) SaveFile (处理提交文件) package mainimport...

听夜深窗外风
33分钟前
4
0
c++ 强制类型转换

强制类型转换 p545

天王盖地虎626
35分钟前
7
0
再读Golang中的异常处理

一起重温Golang中的异常处理啊😸 1.Golang语言中没有其他语言中的try...catch...语句来捕获异常和异常恢复 2.在Golang中我们通常会使用panic关键字来抛出异常,在defer中使用recover来捕获...

Andy-xu
54分钟前
10
0
TiDB 最佳实践系列(三)乐观锁事务

作者:Shirly TiDB 最佳实践系列是面向广大 TiDB 用户的系列教程,旨在深入浅出介绍 TiDB 的架构与原理,帮助用户在生产环境中最大限度发挥 TiDB 的优势。我们将分享一系列典型场景下的最佳实...

TiDB
今天
16
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部