Java设计和使用异常的最佳实践

http://www.ituring.com.cn/article/155一.异常介绍

任何的异常都是Throwable类,并且在它之下包含两个字类Error/Exception,而Error仅在当在Java虚拟机中发生动态连接失败或其它的定位失败的时候,Java虚拟机抛出一个Error对象。典型的简易程序不捕捉或抛出Errors对象,你可能永远不会碰到需要实例化Error的应用,那就让我们关心一下Exception。

UncheckedException.:包括Error与RuntimeException.这类异常都是RuntimeException的子类。

CheckedException:除了Error与RuntimeException,其他剩下的异常.这类异常都是Exception的子类。在编译时在语法上必须处理的异常,因此必须在语法上以try..catch加以处理。

大体上说,有三种不同的“情景”会导致异常的抛出:

编程错误导致异常(Exception due Programming errors):这种情景下,异常往往处于编程错误(如:NullPointerException或者IllegalArgumentException),这时异常一旦抛出,客户端将变得无能为力。

客户端代码错误导致异常(Exception due client code errors):说白点就是客户端试图调用API不允许的操作。

资源失败导致异常(Exception due to resource failures):如内存不足或网络连接失败导致出现异常等。这些异常的出现客户端可以采取相应的措施来恢复应用程序的继续运行。

二.设计异常的最佳实践(BestPractisesforDesigningtheAPI)1.当要决定是采用checkedexception还是Uncheckedexception的时候,你要问自己一个问题,“如果这种异常一旦抛出,客户端会做怎样的补救?”

[原文:Whendecidingoncheckedexceptionsvs.uncheckedexceptions,askyourself,"Whatactioncantheclientcodetakewhentheexceptionoccurs?"]

如果客户端可以通过其他的方法恢复异常,那么这种异常就是checkedexception;如果客户端对出现的这种异常无能为力,那么这种异常就是Uncheckedexception;从使用上讲,当异常出现的时候要做一些试图恢复它的动作而不要仅仅的打印它的信息。

此外,尽量使用uncheckedexception来处理编程错误:因为uncheckedexception不用使客户端代码显示的处理它们,它们自己会在出现的地方挂起程序并打印出异常信息。JavaAPI中提供了丰富的uncheckedexcetpion,譬如:NullPointerException,IllegalArgumentException和IllegalStateException等,因此我一般使用这些标准的异常类而不愿亲自创建新的异常类,这样使我的代码易于理解并避免的过多的消耗内存。

2.保护封装性(Preserveencapsulation)

不要让你要抛出的checkedexception升级到较高的层次。例如,不要让SQLException延伸到业务层。业务层并不需要(不关心?)SQLException。你有两种方法来解决这种问题:

l转变SQLException为另外一个checkedexception,如果客户端并不需要恢复这种异常的话;

l转变SQLException为一个uncheckedexception,如果客户端对这种异常无能为力的话;

多数情况下,客户端代码都是对SQLException无能为力的,因此你要毫不犹豫的把它转变为一个uncheckedexception,看看下边的代码:

publicvoiddataAccessCode(){

try{

..somecodethatthrowsSQLException

}catch(SQLExceptionex){

ex.printStacktrace();

}

}

上边的catch块紧紧打印异常信息而没有任何的直接操作,这是情有可原的,因为对于SQLException你还奢望客户端做些什么呢?(但是显然这种就象什么事情都没发生一样的做法是不可取的)那么有没有另外一种更加可行的方法呢?

publicvoiddataAccessCode(){

try{

..somecodethatthrowsSQLException

}catch(SQLExceptionex){

thrownewRuntimeException(ex);

}

}

上边的做法是把SQLException转换为RuntimeException,一旦SQLException被抛出,那么程序将抛出RuntimeException,此时程序被挂起并返回客户端异常信息。

如果你有足够的信心恢复它当SQLException被抛出的时候,那么你也可以把它转换为一个有意义的checkedexception,但是我发现在大多时候抛出RuntimeException已经足够用了。

3.不要创建没有意义的异常(Trynottocreatenewcustomexceptionsiftheydonothaveusefulinformationforclientcode.)

看看下面的代码有什么问题?

publicclassDuplicateUsernameException

extendsException{}

它除了有一个“意义明确”的名字以外没有任何有用的信息了。不要忘记Exception跟其他的Java类一样,客户端可以调用其中的方法来得到更多的信息。

我们可以为其添加一些必要的方法,如下:

publicclassDuplicateUsernameException

extendsException{

publicDuplicateUsernameException

(Stringusername){….}

publicStringrequestedUsername(){…}

publicString[]availableNames(){…}

}

在新的代码中有两个有用的方法:reqeuestedUsername(),客户端可以通过它得到请求的名称;availableNames(),客户端可以通过它得到一组有用的usernames。这样客户端在得到其返回的信息来明确自己的操作失败的原因。但是如果你不想添加更多的信息,那么你可以抛出一个标准的Exception:

thrownewException("Usernamealreadytaken");

更甚的情况,如果你认为客户端并不想用过多的操作而仅仅想看到异常信息,你可以抛出一个uncheckedexception:

thrownewRuntimeException("Usernamealreadytaken");

另外,你可以提供一个方法来验证该username是否被占用。

很有必要再重申一下,checkedexception应该让客户端从中得到丰富的信息。要想让你的代码更加易读,请倾向于用uncheckedexcetpion来处理程序中的错误(Preferuncheckedexceptionsforallprogrammaticerrors)。

4.Documentexceptions.

publicvoidtestIndexOutOfBoundsException(){

ArrayListblankList=newArrayList();

try{

blankList.get(10);

fail("ShouldraiseanIndexOutOfBoundsException");

}catch(IndexOutOfBoundsExceptionsuccess){}

}

上边的代码在请求blankList.get(10)的时候会抛出IndexOutOfBoundsException,如果没有被抛出,将fail("ShouldraiseanIndexOutOfBoundsException")显示说明该测试失败。通过书写测试异常的单元测试,你不但可以看到异常是怎样的工作的,而且你可以让你的代码变得越来越健壮。

三.使用异常的最佳实践(BestPracticesforUsingExceptions)1.总是要做一些清理工作(Alwayscleanupafteryourself)

如果你使用一些资源例如数据库连接或者网络连接,请记住要做一些清理工作(如关闭数据库连接或者网络连接),如果你的API抛出Uncheckedexception,那么你要用try-finally来做必要的清理工作:

publicvoiddataAccessCode(){

Connectionconn=null;

try{

conn=getConnection();

..somecodethatthrowsSQLException

}catch(SQLExceptionex){

ex.printStacktrace();

}finally{

DBUtil.closeConnection(conn);

}

}

classDBUtil{

publicstaticvoidcloseConnection

(Connectionconn){

try{

conn.close();

}catch(SQLExceptionex){

logger.error("Cannotcloseconnection");

thrownewRuntimeException(ex);

}

}

}

DBUtil是一个工具类来关闭Connection.有必要的说的使用的finally的重要性是不管程序是否碰到异常,它都会被执行。在上边的例子中,finally中关闭连接,如果在关闭连接的时候出现错误就抛出RuntimeException.

2.不要使用异常来控制流程(Neveruseexceptionsforflowcontrol)

下边代码中,MaximumCountReachedException被用于控制流程:

publicvoiduseExceptionsForFlowControl(){

try{

while(true){

increaseCount();

}

}catch(MaximumCountReachedExceptionex){

}

//Continueexecution

}

publicvoidincreaseCount()

throwsMaximumCountReachedException{

if(count>=5000)

thrownewMaximumCountReachedException();

}

上边的useExceptionsForFlowControl()用一个无限循环来增加count直到抛出异常,这种做法并没有说让代码不易读,但是它是程序执行效率降低。

记住,只在要会抛出异常的地方进行异常处理。

3.不要忽略异常

当有异常被抛出的时候,如果你不想恢复它,那么你要毫不犹豫的将其转换为uncheckedexception,而不是用一个空的catch块或者什么也不做来忽略它,以至于从表面来看象是什么也没有发生一样。

4.不要捕获顶层的Exception

uncheckedexception都是RuntimeException的子类,RuntimeException又继承Exception,因此,如果单纯的捕获Exception,那么你同样也捕获了RuntimeException,如下代码:

try{

..

}catch(Exceptionex){

}

一旦你写出了上边的代码(注意catch块是空的),它将忽略所有的异常,包括uncheckedexception.

5.Logexceptionsjustonce

Loggingthesameexceptionstacktracemorethanoncecanconfusetheprogrammerexaminingthestacktraceabouttheoriginalsourceofexception.Sojustlogitonce.

总结

这里给出了一些关于异常处理的一些最佳实践,我并不想开始另一轮的关于checkedexception和uncheckedexception的争论。你可以根据自己的实际情况定制自己异常处理,我坚信我们将有更好的办法来处理我们代码中的异常。

头脑心灵再加上双脚的才是推销员。

Java设计和使用异常的最佳实践

相关文章:

你感兴趣的文章:

标签云: