`

也就说说异常那些事儿

阅读更多

前言

首先本文的阐述源于本人自身对异常的理解,以及总结归纳自身实践以及网络所带来经验。

其次是本文的目的,源于再次探讨企业级开发中的Try......Catch性能问题 一文,我对此文的实践结果无异议,但是其立论实在值得商榷,故书此文共同探讨之。

何谓异常

很多人在讨论异常的时候很模糊,仿佛所谓异常就是try{}catch{},异常就是Exception,非常的片面,所以导致异常影响性能,XXXX……等很多奇怪的言论,所以在此我意在对异常正名。以下,我将异常这个很宽泛,容易被曲解的词进行严格的划分。

异常机制

所谓异常机制也就是指的语言平台支持异常这种错误处理模式的机制,比如c#里的Exception对象,try{}catch{}finally{}结构,throw抛出异常的语句,等等,均为c#语言里对异常机制的实现。

异常机制是随着语言而存在的,一种语言既然支持异常机制,那么异常就是不可回避的,哪怕你自己不throw异常,你所使用的系统类,也会抛出异常给你,所以说讨论在系统里用不用异常是非常可笑的事情。异常机制就像系统的后门,当一个过程执行中系统出错的时候,或者你认为系统不正常的时候,就把当时的情况拍个快照生成异常对象,通过特殊的通道通知调用这个过程的方法。

异常对象

异常对象是异常机制中用来描述异常,记录错误信息,可以说是错误发生是的快照,就跟你开快车闯红灯被天眼拍到的照片差不多。

抛出异常

所谓抛出异常就是异常机制中通知调用的方法本方法出错了的手段。而抛出异常也是很多人诟病异常对性能影响的地方,因为系统性能的开销都是由抛出异常所引起的。但是抛出异常也无法避免,因为这是系统本身的特点,你不抛出异常,系统自己也会抛出,一旦系统抛出了异常对象,你怎么来避免这个性能的开销?没辙

比如方法A调用了方法B,方法B访问数据库,出错了,那么B就会抛出异常,而A就需要catch这个异常来处理。

抛出异常有两种形式,一种是系统抛出的异常,一种是我们自己认为在处理一段逻辑的时候当逻辑错误,就可以通过抛出一个异常对象,来通知调用这个方法的方法,这里出错了。

系统抛出异常

系统抛出的异常对编程人员来说是透明的,也就是我们不需要关心系统是如何得知出错了的,系统类库一旦出错就会将异常对象抛给我们调用它的方法,因为系统本身并不知道要如何处理这个错误。

用户抛出异常

用户抛出异常通常在自己写类库,定义一套API给别人使用的时候用到,这个时候我们并不知道要如何处理这些逻辑错误,所以就需要交给知道如何处理逻辑错误的方法去处理。

在C#里通过定义了throw关键字来抛出异常对象,这里除了抛出新创建的异常对象,也可以通过将已经catch到的异常重新抛出。如果不知道如何处理这个异常,重新抛出异常是很好的习惯,因为既然已经抛出了异常,那么性能已经损失了,所以也不在乎多这么一丁点来更好的挽回错误的结果。

处理异常

所有的异常必须得到妥善的处理,也就是说你必须处理所有的异常。因为系统出错就抛异常,所以这个是c#骨子里的东西,无法避免,所以,系统里到处都会充斥着try{}catch{}。

但是try{}catch{}本身并不会影响系统的性能,所以在没有异常发生的时候try{}catch{}是不会让你系统变慢的,而一旦发生系统异常,你不处理系统就崩溃掉了,你到底是愿意系统慢一点点然后处理掉这个错误呢还是愿意系统崩溃掉呢?

对于处理异常这一点其实我觉得java那种比较严格的,要求严格声明并处理所有异常的方式比较好,能够强制让你重视起异常这回事来,免得系统崩溃掉才想起自己没处理这个异常。

 

如何处理

如何正确的处理异常?这是个很多c#程序员都没最终搞明白的话题,很多人所了解的也无非就是不要用异常处理当业务逻辑,但是何谓用异常来处理逻辑就不得而知了。这里我来详细说明一下,什么地方要用异常,什么地方不要用。

首先有一个前提,异常机制是一个由底向上的冒泡的过程,所以正常的逻辑是,异常由底层抛出,由高层来处理。

处理异常正确的例子

要编写健壮的引用程序,首先要保证必须处理所有的系统异常,也就是调用.NET类库的方法的时候,这个方法可能抛出的异常

例:

image

我们可以看到,GetResponse方法会抛出两个异常 InvalidOperationException和WebException。那么我们就需要将调用的代码所在的catch单独处理这两个异常

image

如果你在这个方法中不知道应该如何处理这个异常,比如这个方法不上不下,和表现层离了好几层,而又需要在表现层通知用户或者由上层业务来决定是通知用户还是悄悄的进村,又或者是悄悄的重新尝试一次,那么自己不能决定的事情就要抛给上层去处理。

image

有人可能会说.NET可以统一处理异常的,不过我不推荐那种大而全的处理方式,不够细致,很多时候会被奇怪的错误搞得你很追查错误的本源。且比如说遇到网络问题重试就没法处理。

异常必须就近处理,这样才能方便追查,而且注意那个“xxx方法出错了”那个地方,很重要,这个描述可以让你在系统排错的时候少走很多弯路,尽量写详细点。

如果你的代码是给别的程序员使用,而不是和最终用户直接交互,那么除了处理所有系统抛出的异常以外,还需要用异常来验证过滤入口参数。比如,假设你要给其他程序员提供一个将用户对象插入数据库的方法:

    public void InsertUser(User user)
    {
        if(user==null)
        {
            throw new ArgumentNullException("参数user为null");
        }
        //调用Orm
    }
<!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com -->

这里我们验证了入口参数,并尽早的抛出了异常,因为这里抛出的异常和不处理等db操作抛出异常,肯定是这里手动抛出的开销更小。这里有一个原则就是,如果这个参数会造成底层代码直接出错,那么就就近处理它,而不要放任其在底层造成系统异常的抛出。当然还有一个原则就是不要在这里判断业务逻辑,比如上面的例子就不要在这里验证User的属性的数值是不是合法之类的。

 

处理异常错误的例子

1:用异常验证用户输入

用户输入的合法性验证是属于业务逻辑的一部分,绝对不要用异常去处理,注意,是用户输入,所以这个经验仅限于表现层逻辑

典型的错误1:

try
{
    int i=int.Parse(textBox1.Text);
}
catch(Exception ex)
{
    alert(“不要输入非数字”);
}
<!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com -->

典型错误2:

void ValidateInput(int i)
{
    if(i<0&&i>100)
    {
        throw new Exception("输入数据范围错误");
    }
}
<!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com -->

 

以上两种错误都是错误使用异常的典型,

2:将异常延迟到底层

这一点我们在正确的例子里提到过

典型错误3:

        try
        {
            string name=Request.QueryString["xx"];
            List<User> userls=User.QueryUserByName(name);
        }
        catch(SqlException ex)
        {
        }
<!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com -->

这个错误在于完全不验证用户输入而直接把数据的验证抛向数据库,等待数据库报错来判断用户输入的正确性,这个是非常致命的错误,很多注入漏洞都是由此产生的。

3:完全不用异常机制

产生这个错误肯定是一个非常脑残的决定造成的。不过很多时候某些不了解异常机制的人,由于对异常的性能开销的恐惧感,经常会做出这么脑残的决定。

典型错误4:

    public bool InsertUser(User user,ref int errcode)
    {
        if(user==null)
        {
            errcode=110;//参数为空错误的代码
            return false;
        }
        //调用Orm
    }
<!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com -->

感觉就是一夜回到了解放前,性能倒是高了,但是系统异常怎么办呢?一旦数据库出错就只等着系统崩溃了。某些有经验的说我会把下面的try{}catch{}起来,不过那不是脱了裤子放屁么,异常都抛出来了,开销已经产生了,结果换来的是牺牲了异常对象的丰富信息而换来了畸形的系统逻辑。性能也没得到提高。

异常对性能的影响

异常机制是C#的特征,因此决定了你不可能逃避,所以讨论异常给你带来了多大开销都是扯淡,没有必要的研究,从根本上无法解决问题。我们应该弄清楚的是,异常的抛出给系统带来什么样的影响,如何在保证系统健壮性的基础上减小不必要的性能消耗。

1.异常的性能开销随着调用栈的深度增加而增大。

对比测试:

测试代码1

            Stopwatch sw = new Stopwatch();
            sw.Start();
            for (int i = 0; i < 10; i++)
            {
                try
                {
                    throw new Exception("test");
                }
                catch { }
            }
            sw.Stop();
            MessageBox.Show(sw.ElapsedMilliseconds + "ms");
<!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com -->

测试结果:

调用次数 第一次 第二次 第三次 第四次
时间 image image image image

 

测试代码2

           Stopwatch sw = new Stopwatch();
            sw.Start();
            for (int i = 0; i < 10; i++)
            {
                try
                {
                    System.IO.File.OpenRead("c:\\不存在的txt.txt");
                }
                catch { }
            }
            sw.Stop();
            MessageBox.Show(sw.ElapsedMilliseconds + "ms");
<!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com -->

 

测试结果

调用次数 第一次 第二次 第三次 第四次
时间 image image image image

小结:由此我们可以发现随着调用栈的深入,性能开销也越大,所以异常应该尽早抛出。

2.对输入的数据应该在业务逻辑中严格检查。

3.try{}catch{}不会造成任何的系统开销,造成系统开销的是throw 抛出异常,这是再三强调的了

诸如:

image

这种言论就纯属脑残了。

 

 

结论

 

异常机制是C#内置的错误处理机制,你无法避免它,唯一正确的道路是学会如何正确使用

希望大家正确使用异常,好好学习天天向上

分享到:
评论

相关推荐

    H3C《交换那些事儿》系列维护资料.rar

    01.《交换那些事儿》- 基础维护篇 - 流统 02.《交换那些事儿》- 基础维护篇 - 镜像 03.《交换那些事儿》- 基础维护篇 - IRF升级指导 04.《交换那些事儿》- 基础维护篇 - IRF替换指导 05.《交换那些事儿》- 基础...

    说说低功耗开发的那些事儿

    例如,在某些电池供电的产品中,可能几毫安甚至微安级别的电流就被认为是低功耗。因此,低功耗的界定往往取决于产品的实际应用场景和设计目标。 #### 二、低功耗开发的重要性 随着物联网(IoT)、可穿戴设备等领域的...

    linux的那些事儿全集

    Linux那些事儿之我是Block层 Linux那些事儿之我是EHCI主机控制器 Linux那些事儿之我是Hub Linux那些事儿之我是PCI Linux那些事儿之我是SCSI硬盘 Linux那些事儿之我是Sysfs Linux那些事儿之我是UHCI Linux那些事儿之...

    linux 那些事儿全集

    《Linux那些事儿全集》是一份详尽的Linux系统学习资料集合,涵盖了从底层硬件接口到上层软件服务的各种主题。这份压缩包包含了多个PDF文档,每个文档专注于讲解Linux系统中的一个特定方面,旨在帮助读者深入理解...

    Linux那些事儿之全集

    导读.doc Linux那些事儿之我是Block层.pdf Linux那些事儿之我是EHCI主机控制器.pdf Linux那些事儿之我是Hub.pdf Linux那些事儿之我是USB_core.pdf Linux那些事儿之我是U盘.pdf等等 Linux那些事儿系列全在这里了

    H3C《交换那些事儿》系列维护资料【第2期】.rar

    25.《交换那些事儿》-技术专题篇-DRNI组网故障处理机制 26.《交换那些事儿》-技术专题篇-DRNI+VRRP及DRNI+VLAN双活组网配置介绍 27.《交换那些事儿》-技术专题篇-DRNI组网ARP及MAC表项同步机制介绍 28.《交换那些...

    Linux那些事儿

    《Linux那些事儿》分为9个部分。 Linux那些事儿之我是U盘 Linux那些事儿之我是Hub Linux那些事儿之我是USB Core Linux那些事儿之我是UHCI Linux那些事儿之我是EHCI控制器 Linux那些事儿之我是PCI Linux那些事儿之我...

    LINUX\Linux那些事儿系列

    在“LINUX\Linux那些事儿系列”中,我们探索了Linux操作系统的核心概念和技术,这个系列涵盖了多个关于Linux内核和设备驱动程序的关键主题。以下是基于提供的文件名所涉及的几个重要知识点的详细说明: 1. **Linux...

    java那些事儿chm

    在这个压缩包中,包含的文件名为“java那些事儿.chm”。 Java,作为世界上最流行的编程语言之一,拥有广泛的应用领域,从企业级应用到移动开发,无处不在。这个CHM文档很可能是对Java基础知识、进阶概念、实战技巧...

    交互设计那些事儿

    在本文中,我们将深入探讨“交互设计那些事儿”这一主题,它结合了原网络文章“交互设计那些事儿一”和“交互设计那些事儿二”的精华内容。这篇文章不仅适合初学者,也对有一定经验的设计师有启示作用。 首先,我们...

    Linux那些事儿系列

    本系列围绕"Linux那些事儿",深入探讨了Linux USB设备开发的相关知识点,包括UHCI(通用主机控制器接口)、Hub、USB core和Sysfs等核心概念。 首先,让我们来了解UHCI。UHCI(Universal Host Controller Interface...

    【代码】CSS那些事儿书中源码.rar

    《CSS那些事儿》是一本深入探讨CSS技术的书籍,作者林小志以其丰富的经验和深入的理解,为读者揭示了CSS在网页设计中的各种奥秘。这本书不仅涵盖了基础的CSS语法,还包括了许多高级技巧和最佳实践,旨在帮助读者提升...

    Linux那些事儿系列.rar

    》包括《Linux那些事儿之我是Hub》、《Linux那些事儿之我是Sysfs》《Linux那些事儿之我是UHCI》、《Linux那些事儿之我是USB core》、《Linux那些事儿之我是U盘》,令人叹为观止的一个linux系列书籍。只能说,江山代...

    Linux那些事儿1-9合集

    读过《linux那些事儿之我是U盘》的人,都知道其风格,我就不多说了。 导读: linux那些事儿之我是U盘 linux那些事儿之我是HUB linux那些事儿之我是USB Core linux那些事儿之我是UHCI Linux那些事儿之我是EHCI主机控制...

    Java编程那些事儿

    "Java编程那些事儿"无疑是对这个强大语言的深入探讨,旨在帮助开发人员提升技能,拓宽视野。这份资料可能是由一系列章节或主题组成的文档,比如基础语法、面向对象编程、异常处理、集合框架、多线程、IO流、网络编程...

    NIOSII那些事儿 Qsys版

    《NIOSII那些事儿》Qsys版:深入理解与实践 **重要知识点解析:** 1. **Qsys在Nios II开发中的应用:** - **Qsys**是Altera公司(现已被Intel收购)提供的一个图形化系统集成工具,主要用于在Quartus II环境中...

    Linux那些事儿(全集)

    Linux那些事儿(全集)

Global site tag (gtag.js) - Google Analytics