zoukankan      html  css  js  c++  java
  • 一起谈.NET技术,谈谈我处理异常的一般方法 狼人:

      我们在编写程序的时候会遇到各种各样的意外情况,如除数为0,数组越界,非法转型,栈溢出等等。因而我们需要有一种机制来处理这些情况,异常处理就是其中的一种机制。当然,还有其他的机制,在MFC中,由于标准的不统一,就存在着各种错误报告方法,如有通过函数返回特殊值的方式,有通过执行某一语句后查询特殊语句获取错误的码的方式,等等。

      在C#中,只有一种报告方式,即异常。这样可以让开发人员从大量的文档中解脱出来,不必为一些非逻辑的问题而花费大量时间。

      C#中的异常不同于C++,所有的异常类型都是继承自System.Exception的,因此我们定义自己的异常类型时,都要继承自System.Exception或者该类的子类。

      常见的异常语句如下:

    try{
      …
    }catch( InvalidOperationException ex){
      ..
    }catch( Exception ex){
      ..
      throw ex;
    }
    }finally{
      ..
    }
    

      我们可以通过捕获不同类型的异常来捕获我们需要的信息,而最后一个捕获Exception的语句将会捕获未被该语句之前的语句捕获的信息。Throw语句可以将捕获的异常抛出,继续引发该异常,使得外层的捕获语句捕获到该异常,当然,最原始的异常也是通过该语句引发的。

      以上都是一些基本的异常知识。我希望根据自己的经验,阐述一下自己开发过程中是如何使用异常的。

      首先要解决的问题是何时使用异常。我们知道,引发一个异常,需要一个比较大的开销(http://www.cnblogs.com/aoaoblogs/archive/2009/12/08/1619827.html)。我认为,对于在处理问题主要逻辑范围以内的问题,不要使用异常。主要逻辑范围,指的是在完成一件任务的主要路径。

      比如我现在要进行一个查询工作,查询一下用户在不在,如果该用户在,那么我应该返回true,不在则应该返回false,我在用这个方法的时候,我对他的直接期望是得到一个返回值。然而,在查询过程中会发生一些其他错误,如数据库无法连接,数据库并发连接数过大。这些问题,就应当是异常处理所要面对的问题。又比如我希望上传一个文件,我用的时候就直接调用该方法,该语句在设计之初,就应当是直接能上传的。

      还有就是什么样的问题需要抛出。

      我认为,在设计过程中,在一些不可恢复的情况下,才有抛出异常的必要。应当在文档中列出较少的可能抛出的异常。我们继续以查询用户为例子,在查询数据库的过程中,我们发现数据库无法连接,如果该查询方法对时间要求不是很严格,我们应当使用一些策略来使得该方法能完成,如再次尝试连接。在尝试连接一定次数之后,我们才需要引发异常。

      还有我认为,我们在设计方法的时候,要捕获所有的方法内语句产生的异常,并将异常包装后抛出,而不是直接抛出。并且抛出的异常最好是与逻辑相关的。继续用查询用户做例子,数据库链接出错、网络不可用之类的问题,我认为为了方便开发者使用该类(其实有时候开发者往往就是自己),我们应当将这些异常都放到一个异常类型中,如EnvironmentException,然后定义一个枚举,将该枚举作为EnvironmentException的属性。我们在捕获异常的时候,只要捕获该类型的异常就好了,如果希望了解异常细节,我们则可以使用InnerException获取引起该异常的异常。

      下面讨论怎样定义异常的问题。

      在.net中有许多内置的异常,如InvalidOperationException、InvalidCastException等,这些异常都继承自System.SystemException。我在定义异常的时候,通常直接继承自System.Exception。我个人认为,一个方法不应该抛出太复杂的异常,而使得开发者困惑。我抛出一个异常,只是把一个方法执行过程中所存在的问题抛出了。我想,得到一个与这个方法逻辑相关的异常、看到名字就大概知道哪里出错的异常,总比一个InvalidCastException这样的异常好多了。

      比如以下是一段语句。

    try{  
      var user=userManager.GetUser(id);
      user.Login();
      user.Logout();
      var article=articleManager.getArticle(articleId);
      article.user=user;
      article.save();
    }catch{
      …
    }
    

      如果Login()会抛出一个InvalidOperationException,logout()会抛出一个InvalidOperationException,我们可能不得不进一步分析这些异常,然后才能对这些操作作出适当的应对措施。

      对于一个规模不大的类,我习惯于定义包含一个枚举类型的类的异常。例如,上面这个例子,我会定义UserOperationException, UserManagerOperationException, ArticleOperationExcetpion,ArticleOperation这些异常,以UserOperationException为例子

    public class UserOperationException:Exception{
      //仅列出主要部分,构造函数等就略了..
      public enum UserOperationStatus{get;set;}
    }
    public enum UserOperationStatus{
      Login,
      Logout,
      Save,
    }
    

      这样在异常捕获的时候,我们可以捕获特定的异常来确定哪一块出现了问题,也可以捕获Exception来捕获所有的异常。

      根据我前面说过的设计原则,所有的异常都是经过包装的已知异常,而且这些异常是基于我们的业务逻辑的,这样代码看起来就会比较清晰了。

      如果Login方法还有一些其他的异常信息,我一般这样实现。

    public enum LoginOperationException:UserOperationException{
      public LoginOperationException(string message,ExceptioninnerException)
        :base(message,innerException,UserOperationStatus.Login)
      {
        ..
      }
    }
    

      使用这样的继承方式,我们就可以更加灵活的来进行异常处理。

      现在讨论一下如何进行异常捕获。

      我认为在写程序的时候,可以首先考虑程序的逻辑问题,非逻辑问题,即异常捕获可以不进行处理。继续以用户登录为例子:

    public function void Login(string username,string password){
      //_UserManager是类的一个私有字段
      var user=_UserManager.GetUserByName(username);
      if(user.password==password)
      {
        user.Login();
        Console.WriteLine(“Success”);
        user.Logout();
        Console.WriteLine(“LogoutSuccess”);
      }else{
        Console.WriteLine(“Invalidpassword”);
      }
    }
    

      可能我们第一次考虑的时候是这样的,这是一个最直观的逻辑,在编写代码的过程中,并没有过多的来关注各个方法抛出的异常。当我们检查完毕整个逻辑没有问题之后,就可以对异常进行捕获了。

    public function void Login(string username,string password){
      try{
        //_UserManager是类的一个私有字段
        var user=_UserManager.GetUserByName(username);
        if(user.password==password)
        {
          user.Login();
          Console.WriteLine(“Success”);
          user.Logout();
          Console.WriteLine(“Logout Success”);
        }else{
        Console.WriteLine(“Invalid password”);
        }
      }catch(UserManagerOperationException){
        Console.WriteLine(“User doesn’t exist”);
      }catch(UserLoginOperationException ex){
        switch(ex.LoginStatus){
          case LoginStatus.DB:
            Console.WriteLine(“DB errors”);
            break;
          case LoginStatus.Network:
            Console.WriteLine(“Network errors”);
            break;
      }catch(UserOperationExceptionex){
        Console.WriteLine(“Other exception {0}”,ex);
      }
    }
    

      当然了,这样的编码方式还是要在编码过程中来关注这些异常。在我所了解的范围内,微软提供了一个开源的企业库Microsoft Enterprise Library(http://entlib.codeplex.com/),该库提供了一个ExceptionHandling Application Block,我们可以用更加灵活方式来处理这些异常,比如全部在配置文件里面做。

      当然,还有其他的方式来解决异常处理的问题,比如AOP技术。最近一直对AOP比较感兴趣,但并没有相关的实践,因此也没有什么经验可谈。

      如果您完整的看完了我的文章,希望您可以根据您的经验指出文章中存在的问题。这篇文章只是一个我的经验总结。

  • 相关阅读:
    有点忙啊
    什么是协程
    HDU 1110 Equipment Box (判断一个大矩形里面能不能放小矩形)
    HDU 1155 Bungee Jumping(物理题,动能公式,弹性势能公式,重力势能公式)
    HDU 1210 Eddy's 洗牌问题(找规律,数学)
    HDU1214 圆桌会议(找规律,数学)
    HDU1215 七夕节(模拟 数学)
    HDU 1216 Assistance Required(暴力打表)
    HDU 1220 Cube(数学,找规律)
    HDU 1221 Rectangle and Circle(判断圆和矩形是不是相交)
  • 原文地址:https://www.cnblogs.com/waw/p/2162782.html
Copyright © 2011-2022 走看看