`

深入异常处理

阅读更多
异常处理是写一个健壮的程序的非常重要的但经常被忽视的方面。如何去抛出、捕获和处理异常有不同的
方式,并不是每种方式都很有效。
一、设计异常层次:
好处:
1.声明捕获一个异常,可以自动的捕获其子类的异常。
2.可以进行多个catch,对不同的异常进行不同的处理,比如FileNotFoundException和IOException.
3.方法中声明抛throws子句中的异常,函数体可以抛出异常的子类型,子类覆写的类也可以声明抛出异常的子类型。
当设计异常层次的API或者应用时,最好设计一个这类API或者应用异常的基类。比如一个持久化的应用,定义一个
基类异常PersistenceException,然后根据不同的异常定义出ConnectionOpenException, QueryException,
UpdateException, CommitException, and ConnectionCloseException.
二、Checked VS Unchecked Exceptions
大多的Java书籍的建议都是:如果异常可以恢复,那么使用checked,严重的不可恢复的错误使用unchecked异常。
优缺点:
Checked Exceptions
优点:
编译的时候强制处理异常,可以强迫对异常进行恰当的处理,
缺点:
1.Checked Exception根据调用链向上传播异常,上层的函数被迫处理或者抛出大量的异常。
2.Checked Exception成为方法/接口的一部分,移除、添加非常困难。

Unchecked Exceptions
优点:
1.不强制抛出、处理,当仍然可以和checked一样处理,代码更简洁、容易阅读。
2.不会将异常称为方法/接口的一部分,方便API的演化。
2.如果使用更多的Unchecked Exceptions可以养成处理异常的习惯,而不仅仅处理Checked Exception。
缺点:
容易忘记处理,因为没有编译的强制的check。

我们通常会在上层的、集中的几个类里面处理异常,而不是是分散在各处来处理异常,这样Unchecked Exceptions
非常适合,因为有时由于API异常声明的限制,Checked Exception强迫我们必须捕获处理。这种方式更容易一致
的处理异常,代码更容易维护,所以现在越来越推荐这样Unchecked方式。

三、包装Exception
为什么要包装?原因:
1、异常声明会在调用链上向上聚集,如果不包装异常可能导致上层调用的函数声明太多不同的异常。
2、不想将下层组件的细节暴露给上层,比如你定义了抽象的数据访问层,那么你不想让其他的应用知道数据访问层的细节,
比如你不应该向上层抛出SQLException和FileNotFoundException,而可能包装出一个DAOException,然后定义出
异常层次结构。

四、安全的处理异常:
如果处理不当,在catch和finally中声明的异常可能被隐藏掉:
  InputStream input = null;

  try{

    input = new FileInputStream("myFile.txt");

    //do something with the stream

  } catch(IOException e){
    throw new WrapperException(e);
  } finally {
    try{
     input.close();
    } catch(IOException e){
       throw new WrapperException(e);
    }
  }


如果myFile.txt文件不存在,可能导致input为null,而在finally没有判断input != null就close了,这会导致NullException,
并且抛出的WrapperException在异常堆栈中被冲掉。
虽然加了input != null,但是下面代码仍然有问题:
  InputStream input = null;

  try{

    input = new FileInputStream("myFile.txt");

    //do something with the stream

  } catch(IOException e){ //first catch block
    throw new WrapperException(e);
  } finally {
    try{
     if(input != null) input.close();
    } catch(IOException e){  //second catch block
       throw new WrapperException(e);
    }
  }

如果关闭出现了异常,那么第一个catch抛出的异常可能被冲掉。
五、异常增强(Exception Enrichment):
包装异常可能导致以下问题:
1、异常堆栈可能会变得很深,但是我们经常只需要异常跟踪的根。
2、异常信息散布在整个的异常堆栈中,这样可能导致很难判断问题出在哪里。
 public void method3() throws EnrichableException{
     try{
        method1(); 
     } catch(EnrichableException e){
        e.addInfo("METHOD3", "ERROR1",
            "An error occurred when trying to ...");
        throw e;
     }
  }

  public void method2() throws EnrichableException{
     try{
        method1(); 
     } catch(EnrichableException e){
        e.addInfo("METHOD2", "ERROR1",
            "An error occurred when trying to ...");
        throw e;
     }
  }
  
  public void method1() throws EnrichableException {
     if(...) throw new EnrichableException(
        "METHOD1", "ERROR1", "Original error message");   
  }



import java.util.ArrayList;
import java.util.List;

public class EnrichableException extends RuntimeException {
    public static final long serialVersionUID = -1;

    protected List<InfoItem> infoItems =
            new ArrayList<InfoItem>();

    protected class InfoItem{
        public String errorContext = null;
        public String errorCode  = null;
        public String errorText  = null;
        public InfoItem(String contextCode, String errorCode,
                                     String errorText){

            this.errorContext = contextCode;
            this.errorCode   = errorCode;
            this.errorText   = errorText;
        }
    }


    public EnrichableException(String errorContext, String errorCode,
                               String errorMessage){

        addInfo(errorContext, errorCode, errorMessage);
    }

    public EnrichableException(String errorContext, String errorCode,
                               String errorMessage, Throwable cause){
        super(cause);
        addInfo(errorContext, errorCode, errorMessage);
    }

    public EnrichableException addInfo(
        String errorContext, String errorCode, String errorText){

        this.infoItems.add(
            new InfoItem(errorContext, errorCode, errorText));
        return this;
    }

    public String getCode(){
        StringBuilder builder = new StringBuilder();

        for(int i = this.infoItems.size()-1 ; i >=0; i--){
            InfoItem info =
                this.infoItems.get(i);
            builder.append('[');
            builder.append(info.errorContext);
            builder.append(':');
            builder.append(info.errorCode);
            builder.append(']');
        }

        return builder.toString();
    }

    public String toString(){
        StringBuilder builder = new StringBuilder();

        builder.append(getCode());
        builder.append('\n');


        //append additional context information.
        for(int i = this.infoItems.size()-1 ; i >=0; i--){
            InfoItem info =
                this.infoItems.get(i);
            builder.append('[');
            builder.append(info.errorContext);
            builder.append(':');
            builder.append(info.errorCode);
            builder.append(']');
            builder.append(info.errorText);
            if(i>0) builder.append('\n');
        }

        //append root causes and text from this exception first.
        if(getMessage() != null) {
            builder.append('\n');
            if(getCause() == null){
                builder.append(getMessage());
            } else if(!getMessage().equals(getCause().toString())){
                builder.append(getMessage());
            }
        }
        appendException(builder, getCause());

        return builder.toString();
    }

    private void appendException(
            StringBuilder builder, Throwable throwable){
        if(throwable == null) return;
        appendException(builder, throwable.getCause());
        builder.append(throwable.toString());
        builder.append('\n');
    }

六、可插拔的异常处理器:
用户处理去处理、log、增强异常,可以通过把异常处理代理给可插拔的异常处理器可以让用户自定义异常处理。
比如我们定义个异常处理器接口:
 public interface ExceptionHandler {
        public void handle(Exception e, String errorMessage);
    }

我们在以下代码中使用它:
public class Component{
    protected ExceptionHandler exceptionHandler = null;

    public void setExceptionHandler(ExceptionHandler handler){
        this.exceptionHandler = handler;
    }

    public void processFile(String fileName){
        FileInputStream input = null;
        try{
            input = new FileInputStream(fileName);
            processStream(input);
        } catch (IOException e){
            this.exceptionHandler.handle(e,
                "error processing file: " + fileName);
        }
    }

    protected void processStream(InputStream input)
        throws IOException{
        //do something with the stream.
    }
}

异常处理器可以决定是处理、忽略、log、包装还是重新抛出。
可以实现一些标准的异常处理器:
public class IgnoringHandler implements ExceptionHandler{
    public void handle(Exception e, String message) {
        //do nothing, just ignore the exception
    }
}

public class WrappingHandler implements ExceptionHandler{
    public void handle(Exception e, String message){
        throw new RuntimeException(message, e);
    }
}

public class CollectingHandler implements ExceptionHandler{
    List exceptions = new ArrayList();

    public List getExceptions(){ return this.exceptions; }

    public void handle(Exception e, String message){
        this.exceptions.add(e);

        //message is ignored here, but could have been
        //collected too.
    }
}


七、Log异常:
如果我们的应用程序发生了业务错误,我们应该log下来,以便为排错提供信息。那么代码的什么地方应该log异常呢?
有以下几种:
1、Bottom Level Logging
当异常发生时log
缺点:
1)log需要分布在任何发生异常或者第三方库抛出异常的地方,需要非常多的分散的log,难于维护和管理。
2)一个公用的组建可能不知道足够的详细错误信息查找错误的原因。
2、Mid Level Logging
在调用的中间的某个地方,这时候有了足够的信息可以log下来。
缺点:仍然需要非常多分散在各处的log,难于维护和管理
3、Top Level Logging
log集中在少数的的Top Level的调用链上。
可以允许在单个集中的地方捕获并log异常,容易维护。
缺点:
Top Level Logging不知道底层组件到底发生了什么错误,Mid Level使用底层组建到底要做什么。
可以通过增强异常的方式来解决这个问题。

比较推荐使用Top Level Logging,因为非常容易log和维护。

八、验证
1、尽快的抛出异常:
比如以下操作
引用

  check if user already exists
  validate user
  validate address

  insert user
  insert address

而不是
引用

  check if user already exists
  validate user
  insert user

  validate address
  insert address

2、抛出异常还是返回false
如果想交互性的一个一个的让用户纠正错误,那么最好抛出异常。
如果需要批量的发现了一堆错误,然后让用户修正,那么return false
九、使用异常处理模板方法:
异常处理很多是样板式的代码,模板方法可以解决这个问题:
比如:
   Input       input            = null;
    IOException processException = null;
    try{
        input = new FileInputStream(fileName);

        //...process input stream...
    } catch (IOException e) {
        processException = e;
    } finally {
       if(input != null){
          try {
             input.close();
          } catch(IOException e){
             if(processException != null){
                throw new MyException(processException, e,
                  "Error message..." +
                  fileName);
             } else {
                throw new MyException(e,
                    "Error closing InputStream for file " +
                    fileName;
             }
          }
       }
       if(processException != null){
          throw new MyException(processException,
            "Error processing InputStream for file " +
                fileName;
    }

可以使用模板方法,Spring大量使用该方法来处理事务等操作:
public abstract class InputStreamProcessingTemplate {

    public void process(String fileName){
        IOException processException = null;
        InputStream input = null;
        try{
            input = new FileInputStream(fileName);

            doProcess(input);
        } catch (IOException e) {
            processException = e;
        } finally {
           if(input != null){
              try {
                 input.close();
              } catch(IOException e){
                 if(processException != null){
                    throw new MyException(processException, e,
                      "Error message..." +
                      fileName);
                 } else {
                    throw new MyException(e,
                        "Error closing InputStream for file " +
                        fileName;
                 }
              }
           }
           if(processException != null){
              throw new MyException(processException,
                "Error processing InputStream for file " +
                    fileName;
        }
    }

    //override this method in a subclass, to process the stream.
    public abstract void doProcess(InputStream input) throws IOException;
}

 new InputStreamProcessingTemplate(){
        public void doProcess(InputStream input) throws IOException{
            int inChar = input.read();
            while(inChar !- -1){
                //do something with the chars...
            }
        }
    }.process("someFile.txt");


参考:
http://tutorials.jenkov.com/java-exception-handling/index.html
http://onjava.com/pub/a/onjava/2003/11/19/exceptions.html
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics