zoukankan      html  css  js  c++  java
  • 如何优雅的处理异常

      知乎参考文章:https://www.zhihu.com/question/28254987

      日常开发中总会有一些这样那样的异常,在项目没有明确规定的情况下,多数时候我们基本无视了异常的处理,最后的结果就是程序能跑就行,出异常了找开发修复。有时候我们还认为抛异常无用,因为反正已经异常了,业务也无法使用,一般日志里都有,抛不抛出来意义不大。这是不对的。因为好的异常抛出机制,有利于提高用户使用体验,有利于快速定位问题跟提高代码的规范化跟健壮性。

      异常处理大概原则有:

      1、异常信息应该具体明确;

      2、早抛出晚捕获;

      3、方便用户使用,方便开发人员排查问题;

      其中,前两点是必须的,第三点看情况尽量满足。

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

      先解释一下这三个原则的原因,再进行具体示例展示:

      1、异常信息必须具体明确。这个好理解,只有异常信息具体明确,在他人使用时才能得到明确的异常信息,知道哪里出错从而进行排查。尽量避免抛出含义非常模糊的异常,比如“业务处理异常”、“后台处理异常”等,不利于定位问题,而且即使抛到前端,也对用户无用。

      2、早抛出,晚捕获。就是异常要尽早抛出,一般是在发生异常的第一现场进行异常的抛出,而不是在代码的 “使用者” 中捕获异常,然后再进行包装抛出;晚捕获说的是要在有能力处理这个异常的地方进行捕获,而不要一发生异常立即捕获,在没有能力处理异常的时候进行捕获,容易造成异常的丢失,通常是在controller进行异常的捕获;

      3、方便使用,分为对用户跟对技术人员两方面。如果这个异常时用户输入数据错误导致的,最好能返回给前端进行相应提示,帮助用户纠正输入,这是用户体验方面的用途;如果这个异常不便于给用户看,可以将异常信息放到前端某个不显眼的隐藏区域(一般首要是不影响用户体验),在发生异常的时候,技术人员可以比较方便直接查看异常信息而不是去翻程序后台日志,这是对技术人员用途。

      举个栗子,比如我们有个业务操作,要求用户填写一些内容并上传一个包含xml字符串的文本提交到服务端,服务端进行校验并将xml转为json存储(随便想的一个场景)。服务端首先要获取xml文本字符串,进行校验,转换,然后跟其他数据一起存入数据库。

      controller我们通常这么写:

    public void saveXml(MyVo myVo){
        AjaxResult ajaxResult = new AjaxResult();
        try{
            myXmlService.saveXml(myVo);
            ajaxResult.setCode(1);
            ajaxResult.setMessage("保存成功");
        }catch(Exception e){
            e.printStackTrace();
            ajaxResult.setCode(0);
            ajaxResult.setMessage("保存失败");
        } 
    }
    

      service可能这么写:

    public void saveXml(MyVo myVo){
      String xml = myVo.getXml();
      String xmlName = myVo.getName();
        ....其它属性
      String jsonStr = myUtil.xml2Json(xml);
      ....可能有其它操作,有一些nullpointer,indexoutofbound之类的异常
    
      PoJo po = ....组装好的要保存的对象
      myDao.saveXml(po);
    }

      有个myDao:

    public void saveXml(PoJo po){
      //....这里有的程序会有一些sql拼装之类的操作,有时会有一些sql异常
      template.save(po);
    }

      当然还有个xml转json的工具类myUtil:

    public String xml2Json(String xmlStr){
      String jsonStr = null;
        try{
        boolean isXml = checkXml(xmlStr); 
      }cache(Exception e){
        System.out.println("业务处理异常");
      }
      if(isXml){
        //业务处理
        jsonStr = ....
      }
      return jsonStr;
    }
    
    private boolean checkXml(String xml){
        //Pattern pattern =  ..
        //Matcher matcher =  ..
        if(matcher.matches() ){
            return true;
        }else{
        throw new RuntimeException("输入的 xml 字符串不符合xml规范!");
           return false;
        }
    }

      以上代码有没有什么问题呢?

      当然是有的。首先,无论用户怎么操作,得到的结果只有保存成功跟保存失败,没有更加具体的提示。这是我们controller层的异常捕获粒度太粗导致的;其次,我们想得到xml不符合规范的异常返回给用户,结果这个异常在myutil中被捕获并处理了,异常信息被改成了“业务处理异常”,描述太过笼统。应该这么处理:

      1、xml不符合规范的异常,在myutil跟service都不要捕获,让其自由上抛至controller。

       2、controller既要能处理这种比较具体的可能对用户有用的异常,又要能捕获一些不确定的,比如数组越界,空指针等异常。这个问题怎么办呢,直接在controller里catch (Exception  e) 是不行的了,那就要进行本地的异常处理。具体做法如下:

      创建一个本地的异常类BusinessException,本地报的所有异常均继承自该异常类:

    public class BusinessException extends RuntimeException {
        //起因的底层异常
        private Throwable cause;
        //业务异常描述
        private String message;
    
        public BusinessException(String message){
            super(message);
            this.message = message;
        };
    
        public BusinessException(Throwable cause, String message){
            super(message,cause);
            this.message = message;
        }
      public void setMessage(String message) {
          this.message = message;
      }
      public String getExceptionMessage() {
          return exceptionMessage;
      }
    }

      针对本例中的xml内容异常,我们可以定义一个XmlContentException extend BusinessException ,也可以直接new 一个BusinessException,具体这么改:

      原来的代码:

    throw new RuntimeException("输入的 xml 字符串不符合xml规范!");
    

      改为:

    throw new BusinessException("输入的 xml 字符串不符合xml规范!");
    

      myUtil中的红色代码,予以删除。

      controller这么写:

    public void saveXml(MyVo myVo){
        AjaxResult ajaxResult = new AjaxResult();
        try{
            myXmlService.saveXml(myVo);
            ajaxResult.setCode(1);
            ajaxResult.setMessage("保存成功");
        }catch(BusinessException be){
           e.printStackTrace();
            ajaxResult.setCode(0);
            ajaxResult.setMessage(be.getMessage());
      }catch(Exception e){
            e.printStackTrace();
            ajaxResult.setCode(0);
            ajaxResult.setMessage("保存失败");
        } 
    }
    

      这样,我们便区分了提升用户体验的业务相关异常跟无需用户知道的普通程序异常。如果我们想要在controller中知道异常的具体堆栈信息,可以在抛出 BusinessException的时候,使用第二个构造方法,将最原始的异常放进去。

      从便于技术人员排查线上问题的角度,还可以把businessException放到ajaxResult对象返回到前端,放到隐藏区域供开发人员查看。更有甚者,我们甚至可以把报错的行相关的调用对象,被调用对象也放到异常类中,在前端异常信息展示的时候一起打印出来,对于技术人员来说,有了这些信息,空指针,sql语法错误等异常便很容易找到原因了。

      异常信息前端展示部分,不作示例。

      

      

      

  • 相关阅读:
    系统角色权限问题
    解析JQuery Ajax
    JavaScriptSerializer序列化时间处理
    Javascript加载talbe(包含分页、数据下载功能)
    代理模式
    工厂模式
    单例模式
    Oracle内置函数
    Oracle大数据SQL语句优化
    Oracle大数据查询优化
  • 原文地址:https://www.cnblogs.com/nevermorewang/p/7079350.html
Copyright © 2011-2022 走看看