`

Java异常处理原则

 
阅读更多

      关于异常处理的一篇文章

异常处理机制提供了一个统一的机制来识别和响应程序错误.一个有效的异常处理方法可以使得你的程序健壮并易于调试.因为异常可以对我们解答下面这些问题提供些帮助,因此它是一个对我们极有用的调试工具.

错误是什么?什么地方发生错误?为什么发生错误?(即:3W what、where、why)

当异常使用恰当时,所抛出异常的类型可以表明什么程序错误发生了,而Stack Trace可以告诉我们什么地方发生了错误.至于为什么发生错误则可以通过看异常信息和Stack Trace来了解.如果你发现你的异常不能回答以上所有问题,那一定是你没有把它们用对.当调试程序时,有三个原则将会帮助你更好的使用异常即:具体化、早throw及晚Catch。

为了说明异常处理的这三个原则,我们将讨论一个叫作JCheckbook的虚拟个人财务管理系统。Jcheckbook可以用作记录和追踪银行帐户的活动,比如存款、取款及开支票。 Jcheckbook最初的版本以桌面程序运行,但未来的计划要求以HTML和C/S applet 做为客户端的实现。

具体化
Java定义了异常类的层次结构,从Trowable开始, Error和Exception则继承于它,而RuntimeException则继承了Exception.如图所示:

Three Rules for Effective Exception Handling(中文)


这四个类的抽象层次都很高,它们对了解到底程序发生了什么帮助极少,当你有权实例化这些类的时候,最好将它们视为基类,而使用更特殊的子类,Java提供了相当数量的异常子类,并且你也可以对一些特殊的情况定义自己的异常类。

比如说,Java的IO包定义了继承于 Exception的IOException类,更特殊一点就是FileNotFoundException,EOFException,, ObjectStreamException和所有的IOException的子类。每个异常类都描述了一种特定的与IO有关的错误,它们分别表示:文件丢失,文件意外结束,或者一个被破坏的序列化对象流。异常类越具体,对回答程序发生了什么错误就越有帮助。

在Catch异常时,捕捉具体的异常类显得特别重要,比如,Jcheckbook遇到FileNotFoundException可能会要求你提供另外一个文件。如果是EOFException,程序可能继续运行,只不过提示一下在异常抛出之前文件是可以被正确地读出来的。如一个ObjectStreamException异常抛出了,程序则可能通知用户所读的文件已经被破坏了,并且要求需要提供一个备份或其它的文件。
 
Translator comments:
这也就是为什么要将异常类分类,以此让每个类特定地代表一种程序错误的原因?同时也是为什么在方法声明中throws某个特定的异常类,而不是如: throw Exception?也是为什么要在Catch中捕获具体的异常对象,而不是形如:try{}catch(Excetion e ){}的原因
 
因为在Java中,一个try程序块是可以指定多个Catch块,因此就很容易去捕捉具体的异常,然后针对不同的异常提供相应的处理方案。
File prefsFile = new File(prefsFilename);
try
{
    readPreferences(prefsFile);
}
catch (FileNotFoundException e)
{
    // alert the user that the specified file
    // does not exist
}
catch (EOFException e)
{
    // alert the user that the end of the file
    // was reached
}
catch (ObjectStreamException e)
{
    // alert the user that the file is corrupted
}
catch (IOException e)
{
    // alert the user that some other I/O
    // error occurred
}
Jcheckbook为了对所捕获到的异常给用户提供比较具体和特殊点的错误信息,应用了多个Catch块。比如,如果捕获的是FileNotFoundException,它可能通知用户重新指定一个文件。
额外的编写多个Catch块,在某些情况下,可能是多余的负担,但是在这个例子中,它确实可以使程序以一种更加友好的方式回应程序发生的各种错误。
 
如果是IOException而不是我们起初所指定的那三个异常被抛出了,那么最后一个Catch块将处理并提供一个一般意义上的错误信息。这样,程序在提供某些具体的错误信息的同时也可以对意想不到的那些与文件有关的异常提供一般意义上的处理。
 
有时候,开发人员直接捕获Exception,然后显示异常类的名字和Stack Trace信息,但为了具体问题具体处理,请不要这样做。看到屏幕上的Java.io.EOFException或者Stack Trace信息可能会使用户迷惑,而不是帮助他。捕获具体的异常从而用英语或其它语言给用户一个与问题相关提示,同时将异常的Stack Trace放到LOG文件中。异常和Stack Trace对开发人员意味着一个有力的调试工具,而对用户却毫无用处。
 
最后,请注意到Jcheckbook将捕获和处理Exception类型异常推迟到用户界面上,而不是放到readPreferences()函数里。这样在界面上可以以对话框的形式提示的用户相关信息或者使用其它的处理方式。这就是我们待会儿将会讨论的“晚捕获”原则
 
Translator comments:
所谓具体化为:
定义具体的代表某个特定错误的异常类。
方法声明特定的异常类。
方法捕获具体类。
目地:具体问题具体处理。
早抛出                                                                
通过Stack trace向我们展示的引起异常的方法调用顺序、类名、方法名、原代码文件名以及每个方法调用的行号,这样可以帮助我们精确地定位异常发生的地方。考虑下面的Stack trace信息:
Java.lang.NullPointerException at Java.io.FileInputStream.open(Native Method) at
Java.io.FileInputStream.<init>(FileInputStream.Java:103)   at
jcheckbook.JCheckbook.readPreferences(JCheckbook.Java:225)  at
jcheckbook.JCheckbook.startup(JCheckbook.Java:116)   at
jcheckbook.JCheckbook.<init>(JCheckbook.Java:27)  at
jcheckbook.JCheckbook.main(JCheckbook.Java:318)

这表明类FileInputStream的open方法抛出一个NullPointerException异常,但注意到
FileInputStream.open()是Java标准类库的一部分。这样引起异常的原因很可能在我们自己
代码里面,而不Java
API,所以问题一定出在这之前的某个方法内,幸运的是它被显示了出来。

很不幸,异常NullPointerException恰好是Java中能提供有效信息最少的异常类中的一个。
它并不能告诉我们真正想知道的。同时我们也需要向后追踪去找到错误发生地。

通过向后追踪Stack
trace和检查我们的代码,我们发现错误是由于调用方法readPreferences()时传入的文件名
为null,因为方法知道一个空文件名将使方法不能再执行下去,所以它立即检查这个条件:

public void readPreferences(String filename)     throws IllegalArgumentException
{  if (filename  == null)  { throw  new IllegalArgumentException  ("filename is
null"); } //if
    //...perform other operations...
    InputStream in = new FileInputStream(filename);
    //...read the preferences file... }

因为比较早的抛出了异常,所以异常就变得更加具体和准确了。Stack
trace也很准确地反映出发生了什么异常,为什么及在什么地方。这样使得Stack
trace更加准确的反映了本来程序所发生的一切:

Java.lang.IllegalArgumentException:      filename      is      null  at
jcheckbook.JCheckbook.readPreferences(JCheckbook.Java:207) at
jcheckbook.JCheckbook.startup(JCheckbook.Java:116)at
jcheckbook.JCheckbook.<init>(JCheckbook.Java:27)at
jcheckbook.JCheckbook.main(JCheckbook.Java:318)

另外,异常信息(“filename   is null”)指出了是什么为空,从而使得异常附带更多有用的信息。而这些是我们无法从异常NullPointerException中获得的。一旦出错误就立即抛出异常,这样可以避免再去构造或找开那些不再需要的对象或资源。比如说文件或网络连接。与打开这些资源相关的清理工作也可以避免了。
 
晚捕获
许多Java开发人员,无论新手或老手都普遍地犯一个错误就是在程序有能力处理一个异常之前就将它捕获了。Java编译器坚持让Checked Exception不是要被捕获就一定要在函数头中声明的作法也客观上促使了程序员采用上面这一错误作法。对程序员来说,一个很自然的趋势就是让代码包括在try程序块中并捕捉异常以阻止编译器报错。
 
问题是当捕获到异常之后你该如何处理它了?最坏的事情就是不做任何处理。一个空的catch块使异常无端地消失了从而使得关于异常的What、Where、Why信息永远丢失了。将异常的信息Log下来使情况稍好点,毕竟那样还有关于异常信息的记录。但是我们不可能期望用户想看甚至看懂Log文件和Stack trace信息。在函数readPreferences()内显示异常信息对话框是不合适的。因为Jcheckbook目前运行为桌面程序,我们也计划将其改写为基于HTML或者C/S的版本。
 
功能从Server上获取,而错误需要在浏览器或客户端中显示。我们应该带着为未来着想的想法去设计readPreferences()方法。恰当的将用户交互代码从程序逻辑中分开可以增加代码的可重用性。
 
在有能力处理一个异常之前去捕获它,常常会进一步地引起其它的错误和异常。比如,像readPreferences()方法立即捕获并处理了在调用FileInputStream构造函数所产生的FileNotFoundException异常,代码如下:
 
public void readPreferences(String filename)
{
    //..
    InputStream in = null;
    // DO NOT DO THIS!!!
    try
    {
        in = new FileInputStream(filename);
    }
    catch (FileNotFoundException e)
    {
        logger.log(e);
    }
    in.read(...);
    //...
}
 
这段代码在对恢复错误无能为力的情况下捕捉了异常FileNotFoundException。如果文件没有找到,剩下的方法体肯定执行不起来。用一个不存在的文件名去调用方法readPreferences ()将会发生什么?当然,异常FileNotFoundException将会被Log,如果我们恰好去检查Log文件,我们将会意识到异常的发生。但如果程序继续去读那个文件当中的数据,那会发生什么了?因为文件不存在,in是null,所以一个NullPointerException将会被抛出。
 
当是调试程序时,直觉会使我们在日志文件中去检查最近的信息,你会非常恐惧的看到一个NullPointerException异常,因为它太一般了,不能提供你有价值的对判断异常发生原因有帮助的任何信息,Stack trace也是,这不仅使你无法了解错误是什么(真的错误是FileNotFoundException而不是 NullPointerException),而且也无法了解错误发生的正确地点。
 
除了catch异常外,readPreferences()方法还能对异常做何处理了?可能跟我们的直觉相反,仅仅将它上抛就可以了,不要立即catch异常。将处理的责任上抛给readPreferences()的调用者,让它去决定采用合适的方法去处理文件引用丢失的问题,它可以提示用户要求另一个文件、使用默认的值或者如果没有其它的办法,提示用户有问题发生并退出程序的运行也是可以。
 
这种将异常处理的任务上抛给它的调动链上的方法就是用throws关键字在方法头声明这些需要上抛的异常。当声明这些异常时,请尽可能使用能代表具体问题的异常类。这样可以使调动你方法的程序可以预料到到底会发生哪些异常并根椐具体情况处理它们。将前面代码修改如下:
 
public void readPreferences(String filename)
    throws IllegalArgumentException,
           FileNotFoundException, IOException
{
    if (filename == null)
    {
        throw new IllegalArgumentException
                        ("filename is null");
    } //if
    //...
    InputStream in = new FileInputStream(filename);
    //...
}
 
从技术上说,我们唯一需要声明的就是IOException,但是通过声明此方法可能会抛出FileNotFoundException可以帮助调用者更好的处理异常。而异常IllegalArgumentException是不需要声明的,因为它是一个Unchecked Exception(为RuntimeException的子类)仍然将其包括在其中是因为这样使代码更清晰[特别对调用者而言]。
 
当然,最终你的程序仍需捕获异常,要么程序就会意外地中止。但这个原则的意义就在于,它可以让你在合适地方捕获异常并能够提供合适的异常处理方法,不会再引起其它的异常,从而让程序能继续运行。或者提供用户一些具体有用的信息,包括如何从错误中恢复过来一些指示。当一个方法对异常不能做这两者之一时,简单地将异常上抛,这样它就会在合适的地方被捕获并处理。
 
总结
有经验的开发人员都知道调试程序最难的部分不是修改Bug,而是找出Bug的藏身之处。通过使用上面所讲的三个原则,你就可以使异常帮助你追踪和清除Bugs并且使得你的程序更加健壮和友好。
分享到:
评论
3 楼 yycdaizi 2014-08-14  
lanmolsz 写道
无法同意楼主的说法,楼主所谓的晚捕捉就是典型的让异常控制程序的执行流程和逻辑,而一般来说这样是不可取的。因为这种情况我们完全可以使用更好的流程控制语句如if这种。...

关于你说的“晚捕捉就是典型的让异常控制程序的执行流程和逻辑,而一般来说这样是不可取的。因为这种情况我们完全可以使用更好的流程控制语句如if这种。”我无法同意,这应该是异常机制的优势之一。想想没有异常处理机制的C语言中,一个函数要将内部的异常状态通知调用者,只能通过返回状态码来实现,所以程序中经常出现int r=a(); if(r=0){..}这种,要命的时,如果调用者要将这个状态返回更上层,上层也得做一个这样的判断。同一个异常状态,却要调用的每一层都判断一次,这是何等的痛苦。而异常处理机制的出现就解决了这个问题,发生异常的地方抛出异常后,上层如果无法处理,可以不用管,异常会自动往上抛。不知省掉了多少if判断语句。
2 楼 lanmolsz 2014-06-11  
无法同意楼主的说法,楼主所谓的晚捕捉就是典型的让异常控制程序的执行流程和逻辑,而一般来说这样是不可取的。因为这种情况我们完全可以使用更好的流程控制语句如if这种。楼主所举的例子比如FileNotFoundException 这种异常对于开发来讲如果出现则代表无法解决只能简单的记录日志处理。
因为有些是我们假定的一个前提,比如我们程序要求文件必须存在,不存在则代表程序运行不合法,那么这种情况我们是没必要做许多的处理的。
我认为既然讲异常处理原则应该讲,我们什么时候声明Exception?什么时候声明RuntimeException?什么时候什么地方应该抛出?什么时候什么地方应该catch?这些才是重点而非重新表述一边它本身的异常机制。
1 楼 cici_new_1987 2014-03-28  

相关推荐

    Java异常处理机制及应用.pdf

    Java 异常处理原则是指在处理异常时需要遵守的一些基本原则,如尽量少地抛出异常、合理地使用 finally 块、避免 catch-all 异常、使用多 catch 块等。 五、Java 异常处理机制的应用 Java 异常处理机制的应用非常...

    有效处理JAVA异常三原则

    为了深入理解和正确实施这一机制,本文将阐述有效处理Java异常的三个重要原则,并结合JCheckbook类的示例进行讨论。 首先,Java中的异常是由Throwable类的层次结构所定义的,其中包含了Error、Exception以及...

    高效的java异常处理框架高效的java异常处理框架高效的java异常处理框架

    本文将从 Java 异常的基本概念和语法开始,讲述 Java 异常处理的基本知识,分析 Java 异常体系结构,对比 Spring 的异常处理框架,阐述异常处理的基本原则,并提出了自己处理一个大型应用系统异常的思想,并通过设计...

    Java异常处理终结篇——如何进行Java异常处理设计 - 望远的个人页面 - 开源中国社区1

    在进行Java异常处理设计时,我们需要遵循以下原则: 1. **不要直接忽略异常**:捕获到的异常应当被适当处理,无论是记录日志、通知用户还是尝试恢复。忽略异常可能会导致程序行为不可预测,甚至导致程序崩溃。 2. ...

    Java异常处理机制应用研究.pdf

    本文将详细介绍 Java 异常处理机制的应用研究,包括 Java 异常体系统结构、异常分类与处理机制、异常处理的一般原则和异常处理框架等。 Java 异常体系统结构 Java 异常体系统结构如图 1 所示,Throwable 是所有...

    JAVA中异常处理的WORD文档

    异常处理基于匹配原则,即异常对象的类型必须与`catch`后面的异常类匹配。`Throwable`是最顶级的异常类,但通常我们只关心`Exception`及其子类,因为它们更具体且与程序逻辑相关。`Error`类通常表示系统级别的错误,...

    论文研究-基于Java异常处理机制的研究 .pdf

    Java异常处理机制研究的知识点涵盖了异常处理的基本概念、分类、原则以及实际应用等方面。 1. 异常处理概念 异常处理是Java语言中用于处理程序运行时遇到的错误和异常情况的一种机制。它通过异常类的层次结构来实现...

    Java异常处理的设计原则.pdf

    以下是基于给定内容总结的Java异常处理的设计原则: 1. **早处理**:异常不应该用于控制正常程序流程,而应该仅用于处理非预期的情况。一旦发生异常,应尽早捕获并处理,避免异常向上冒泡到程序的更高层次。如果不...

    java开发异常处理与日志规范

    Java开发中的异常处理和日志规范对于保持代码的健壮性和可维护性至关重要。下面将详细阐述这些要点: 1. **避免捕获可预检查的RuntimeException**:在Java中,像NullPointerException和IndexOutOfBoundsException...

    Java异常处理教程

    本教程将深入探讨Java异常处理的基础、原则以及最佳实践。 一、异常的分类 Java中的异常类层次结构主要基于`java.lang.Throwable`类,它有两个主要子类:`Error`和`Exception`。`Error`通常表示系统级的错误,如...

    Java异常处理-异常处理的方式2:throws

    综上所述,Java异常处理中的`throws`关键字提供了将异常处理责任转移给调用者的能力,使得代码结构更加清晰,同时遵循了异常处理的职责分离原则。正确理解和使用`throws`有助于编写出健壮、可维护的Java程序。

    Java异常处理基础-Java教程共1页.pdf.zip

    Java异常处理的原则是尽早发现,尽快处理。通过适当的异常处理,可以避免程序因为未处理的异常而突然崩溃,同时提供给用户更友好的错误信息。 在实际开发中,应遵循以下最佳实践: - 使用具体的异常类:避免使用...

    高效的java异常处理

    因此,异常处理是Java编程中不可或缺的一部分。 在Java中,异常处理机制是通过try-catch-finally语句块实现的。当一个异常在try块中被抛出时,控制权会立即转移到相应的catch块。catch块是用来处理特定类型的异常,...

    基于JAVA常见异常处理研究.pdf

    JAVA异常处理是JAVA语言的重要机制,正确、合理地处理异常对电脑的稳定性和安全性有着极为重要的作用。异常主要有三类,包括异常处理语句的定义、捕捉异常和程序流程跳转等。 一、 JAVA 异常处理机制 JAVA 包括了...

    JAVA异常处理原因方法

    ### JAVA异常处理原因方法 #### 程序错误原因与概念 在软件开发中,程序错误是不可避免的,它们可以大致分为三类:编译错误、运行错误和逻辑错误。 - **编译错误**,也被称为“语法错误”,这类错误是在编译阶段...

    Java异常框架设计

    这篇博文“Java异常框架设计”可能探讨了如何有效地利用Java的异常处理机制来构建可靠的系统。在这个讨论中,我们将深入理解Java异常的基本概念、异常分类、以及如何通过良好的框架设计提升代码的可读性和可维护性。...

    java 异常类处理

    在进行异常处理时,一个良好的实践是遵循“尽早发现,尽早处理”的原则,避免异常传播到程序的高层。此外,异常信息应具有足够的描述性,方便开发者定位问题。通过自定义异常类,可以更好地封装业务逻辑错误,并提供...

    Java有效处理异常的三个原则

    总的来说,Java异常处理的原则是提高程序的健壮性,确保在遇到错误时能够正确响应。通过具体明确地抛出和捕获异常,尽早发现和处理问题,以及延迟捕获以实现更精细的控制,我们可以创建出更稳定、易调试的Java应用...

    Java 异常处理面试集锦及答案20道

    Java异常处理是编程实践中不可或缺的一部分,它为程序员提供了一种优雅的方式来...通过理解并熟练应用上述Java异常处理的知识,开发者可以编写出更加健壮、易于维护的代码,并在面试中表现出对Java编程原则的深刻理解。

Global site tag (gtag.js) - Google Analytics