`

深入异常处理

阅读更多
异常处理是写一个健壮的程序的非常重要的但经常被忽视的方面。如何去抛出、捕获和处理异常有不同的
方式,并不是每种方式都很有效。
一、设计异常层次:
好处:
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
分享到:
评论

相关推荐

    深入理解java异常处理机制

    ### 深入理解Java异常处理机制 #### 引言 异常处理机制是任何现代编程语言不可或缺的一部分,尤其是在像Java这样的面向对象的语言中更是如此。Java的异常处理机制旨在帮助开发者编写更健壮、更易于维护的代码。...

    易语言线程结构异常处理

    易语言是一种专为中国人设计的编程语言,它以简体中文作为编程语句,降低了编程的门槛,使得更多的人能够参与到编程中来。...同时,深入理解线程管理、异常处理和底层内存操作也是提升编程技能的重要步骤。

    c/vc++/MFC异常处理/结构化异常处理 浅析

    本篇文章将深入浅析C、C++中的异常处理机制以及MFC中的异常处理策略。 首先,我们来看C语言的异常处理。C语言本身并不直接支持异常处理,但可以通过返回错误码或者设置全局变量的方式进行错误处理。这种方式称为...

    深入解析结构化异常处理(SEH中文).docx

    结构化异常处理(Structured Exception Handling,SEH)是Win32操作系统中的一个重要特性,用于处理程序执行过程中的异常情况。虽然通常与特定的编译器(如Microsoft的Visual C++或Borland C++)的运行时库相关联,...

    易语言HOOK异常处理

    "New_SE_Handler"可能是一个新的结构化异常处理程序,结构化异常处理(SEH)是Windows操作系统中的一个特性,用于处理硬件和软件异常。 "GetSeAddr"可能是获取异常发生时的地址函数,这对于分析异常原因和定位问题...

    ARM处理器异常处理步骤

    ARM处理器异常处理是指ARM微处理器对各种异常情况作出响应和处理的过程。异常指的是处理器在正常执行程序时遇到的特殊...对于开发者而言,深入理解ARM处理器的异常处理机制对于编写高效、稳定的ARM平台软件至关重要。

    ADS异常处理.pptADS异常处理.pptADS异常处理.ppt

    本文将深入探讨ARM处理器的异常处理机制,包括异常类型、处理流程、异常优先级以及向量表等内容。 一、异常类型 ARM处理器支持多种类型的异常,包括: 1. Reset(复位):系统启动或重启时触发。 2. Data Abort...

    异常处理流程图Exception

    本文将深入探讨异常处理流程,通过分析给定的“异常处理流程图Exception”来理解其核心概念。 ### 异常处理概述 异常处理是一种编程模式,用于捕捉和响应程序运行时可能发生的非正常事件,这些事件被称为异常。在...

    深入研究win32结构化异常处理.doc

    结构化异常处理(Structured Exception Handling,SEH)是Windows操作系统中的一个重要特性,它提供了一种处理程序运行时异常的机制。SEH 不仅仅局限于C++,而是与操作系统底层紧密关联,尽管通常通过C++编译器的...

    使用企业库进行异常处理

    它不仅简化了异常处理的编写,还使得异常策略能够在管理层进行定义和维护,无需深入到代码层面进行更改。 **异常处理应用程序块的核心特性:** 1. **跨层异常处理**:它不仅限于服务接口,而是覆盖整个应用程序...

    vc异常处理文章两篇

    这两篇文章,“Visual C++中的异常处理浅析”和“C与C++中的异常处理.pdf”,旨在深入探讨这个主题。 在C++中,异常处理通过try、catch和throw关键字实现。当程序遇到无法正常处理的错误(如除以零或空指针引用)时...

    易语言SEH异常处理源码.rar

    学习易语言SEH异常处理源码,可以深入了解异常处理机制,这对于编写稳定、健壮的软件至关重要。通过阅读和分析源码,开发者可以学习到如何在易语言中设置和捕获异常,如何定义自己的异常处理器,以及如何在出现异常...

    delphi的异常处理

    本文将深入探讨Delphi中的异常处理机制,包括异常的种类、如何抛出和捕获异常,以及如何有效地利用异常处理来增强程序的健壮性。 首先,我们来看一下Delphi中的异常类型。Delphi使用`Exception`类作为所有异常的...

    J2EE项目中统一异常处理源码

    在J2EE项目开发中,异常处理是一项至关重要的任务,它确保了系统的稳定性和用户体验。一个良好的异常处理机制能够提供详细的错误信息,帮助开发者快速定位问题,并且可以在生产环境中优雅地处理异常,防止用户看到...

    深入探索高效的Java异常处理框架

    本文深入探讨了高效Java异常处理框架,旨在提高代码的健壮性和稳定性。首先,文章介绍了异常的基本概念和Java异常体系结构。 Java将异常视为对象来处理,所有的异常都继承自`java.lang.Throwable`类。Throwable类有...

    C++ Java异常处理比较

    本文将对C++和Java两种语言的异常处理机制进行深入的比较和分析,探讨它们的相似之处以及不同之处,以帮助开发者更好地理解和应用这两种语言。 首先,异常处理的基本目的是捕获并处理运行时错误,这些错误通常包括...

    软件开发中异常处理.pdf

    本文档,"软件开发中异常处理.pdf",深入探讨了异常管理的架构,包括异常的层次结构、处理流程以及.NET框架下的异常处理模型。 首先,异常管理的架构要求系统具备检测异常、记录异常日志、发送异常信息以及生成异常...

    异常处理的两个实验代码

    下面,我们将深入探讨异常处理的基本原理、常用方法以及两个实验代码中的关键点。 异常处理的主要目标是中断正常的代码流程,当发生错误时执行特定的清理操作,并提供有关错误的有用信息。在大多数编程语言中,异常...

    VC异常处理实例有界面

    在编程领域,异常处理是一项关键的技术,特别是在C++和Windows编程中。对于VC++(Visual C++)开发者来说,理解并熟练运用异常处理是至关重要的。在这个实例中,我们将聚焦于VC6中的异常处理机制,它主要依赖于结构...

Global site tag (gtag.js) - Google Analytics