zoukankan      html  css  js  c++  java
  • 谈谈Exception,什么时候抛出?什么时候接住? 一

    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,不是自己的。所以也好控制。不涉及自己的代码维护。 

  • 相关阅读:
    手机端局部滚动问题 overflow-y:auto|scroll无效,使用iscroll解决
    h5 js 图片预览并判断 ajax上传
    安装一些包管理的记录 win10
    nginx php rewrite配置
    记一次u盘 无法格式化、0字节、写保护的解决过程
    erlang,elixir安装
    Redis详解:lists数据类型及操作
    redis类型[string 、list 、 set 、sorted set 、hash]
    java 生成Word文档
    mysql 获取当前月最后一天和第一天
  • 原文地址:https://www.cnblogs.com/zc22/p/1714361.html
Copyright © 2011-2022 走看看