首页 > 编程语言 >Java异常处理的最佳实践

Java异常处理的最佳实践

时间:2024-05-11 11:42:50浏览次数:24  
标签:exception Java 抛出 实践 最佳 checked 异常 客户端

 

 

本文是关于 Exception 处理的一篇不错的文章,从 Java Exception 的概念介绍起,依次讲解了 Exception 的类型(Checked/Unchecked),Exception 处理的最佳实现:

选择 Checked 还是 Unchecked 的几个经典依据

Exception 的封装问题

如无必要不要创建自己的 Exception

不要用 Exception 来作流程控制

不要轻易的忽略捕获的 Exception

不要简单地捕获顶层的 Exception

关于异常处理的问题之一就是要知道何时(when)和如何(how)使用它。在本文中我将介绍一些关于异常处理的最佳实践,同时我也会总结最近关于 checked Exception 使用问题的一些争论。

作为程序员,我们都希望能写出解决问题并且是高质量的代码。不幸的是,异常是伴随着我们的代码产生的副作用(side effects)。没有人喜欢副作用(side effects),所以我们很快就找到(find)了我们自己的方式来避免它,我曾经看到一些聪明的程序员用下面的方式来处理异常:

public void consumeAndForgetAllExceptions() {
try {
//...some code that throws exceptions
} catch (Exception ex){
ex.printStacktrace();
}
}
上边的代码有什么问题么?

一旦抛出异常,正常的程序执行流程被暂停并且将控制交给catch块,catch块捕获异常并且只是 suppresses it(在控制台打印出异常信息),之后程序继续执行,从表面上看就像什么都没有发生过一样……

那下面的这种方式呢?

public void someMethod() throws Exception { }
他的方法体是空的,它不实现任何的功能(没有一句代码),空白方法怎么(how)会(can)抛出异常?JAVA并不阻止你这么做。最近,我也遇到类似的代码,方法声明中会抛出异常,但是没有实际发生(generated)该异常的代码。当我问程序员为什么要这样做,他回答说“我知道这样会影响API,但我已经习惯了这样做而且它很有效。”

C++社区曾经花了数年时间来决定(decide)如何使用异常,关于此类的争论在 java社区才刚刚开始。我看到许多Java程序员艰难(struggle)的使用异常。如果没有正确使用,异常会影响程序的性能,因为它需要使用内存和CPU来创建,抛出以及捕获异常。如果过分的依赖异常处理,会使得代码难以阅读,并使使用API的程序员感到沮丧,我们都知道这将会带来代码漏洞(hacks)和代码异味(code smells),

客户端代码可以通过忽略异常或抛出异常来避开这个问题,如前两个示例所示。

异常的本质

从广义上讲,有三种不同的情景会导致异常的抛出:

编程错误导致的异常 (Exception due Programming errors):这一类的异常是因为编程错误发生的,(如NullPointerException和IllegalArgumentException),客户端通常无法对这些编程错误采取任何措施。

客户端代码错误导致异常(Exceptions due to client code errors):客户端代码试图调用API不允许的操作,从而违反了合约。如果异常中提供了有用的信息,客户端可以通过其采用一些替代方法。例如:当解析格式不正确的XML文件时会抛出异常。该异常中包含导致问题发生的XML内容的具体位置。客户端可以通过这些信息采取恢复措施。

资源失效导致的异常(Exceptions due to resource failures):当资源失效时发生的异常。如内存不足或网络连接失败。客户端对资源失效的回应是要根据上下文来决定的。客户端可以在一段时间之后重试该操作,或是只记录资源失效日志并停止应用程序。

Java 异常类型

Java 定义了两类异常:

1.检查型异常 (Checked exceptions):从 Exception 类继承的异常都是检查型异常(checked exceptions),客户端必须处理API抛出的这类异常,通过catch子句捕获或是通过throws子句继续抛出(forwarding it outward)。

2.非检查型异常 (Unchecked exceptions):RuntimeException 也是 Exception 的子类,然而,从RuntimeException 继承的所有异常都会得到特殊处理。客户端代码不需要专门处理这类异常,因此它们被称为 Unchecked exceptions.

举例来说,下图为 NullPointerException 的继承关系。

 

 

 

图中,NullPointerException 继承自 RuntimeException,所以它是 Unchecked exception.

我见过大量使用 checked exceptions 只在极少数时候使用 Unchecked exceptions。最近,Java社区关于 checked exceptions 及其真正价值进行了热烈讨论,争论源于Java似乎是第一个带有 checked exceptions 的主流<abbr title="面向对象(Object Oriented)">OO</abbr>语言,而C++和C#根本没有 checked exception,它们所有的异常都是unchecked .

从低层抛出的 checked exception 强制要求调用方捕获或是抛出该异常。一旦客户端不能有效地处理这些被抛出的异常,API和客户端之间的异常协议(checked exception contract)就会变成不必要的负担。客户端的程序员可以通过将异常抑制(suppressing)在一个空的catch块中或是直接抛出它。从而又将这个负担交给了客户端的调用者。

Checked exception还被指责可能会破坏封装,看下面的代码:

public List getAllAccounts() throws
FileNotFoundException, SQLException{
...
}
getAllAccounts() 方法抛出了两个检查型异常。调用此方法的客户端必须明确的处理这两种具体的异常,即使它并不知道在 getAllAccounts() 中哪个文件或是数据库调用失败了,

或者没有提供文件系统或数据库逻辑的业务,因此,这样的异常处理导致方法和调用者之间不当的强耦合(tight coupling)。

设计异常的最佳实践 (Best Practises for Designing the API)

在讨论了这些之后,现在让我们来探讨一下如何设计一个正确抛出异常的API。

1. 当要决定是采用 checked exceptions 还是 unchecked exceptions 的时候,问自己这样的一个问题,“如果这种异常一旦抛出,客户端会进行怎样的处理?”

如果客户端可以采取措施从异常中恢复,那就选择 checked exception 。如果客户端不能采取有效的措施,就选择 unchecked exceptions 。有效的措施是指从异常中恢复的措施,而不仅仅是记录异常日志。总结一下:

Client's reaction when exception happens Exception type
Client code cannot do anything Make it an unchecked exception
Client code will take some useful recovery action based on information in exception make it a checked exception
此外,尽量使用 unchecked exception 来处理编程错误:unchecked exception 的优点在于不强制客户端显示的处理它,它会传播(propagate)到任何你想捕获它的地方,或者它会在出现的地方挂起程序并报告异常信息。Java API中提供了丰富的 unchecked excetpion,如:NullPointerException , IllegalArgumentException 和 IllegalStateException 等。我更倾向于使用JAVA提供的标准异常类而不愿创建新的异常类,这样使我的代码易于理解并避免过多的消耗内存。

2. 保护封装性 (Preserve encapsulation)

永远不要让特定于实现的 checked exception 传递到更高层,比如,不要将数据访问层的 SQLException 传递到业务层,业务层并不需要了解(不关心? ) SQLException ,你有两种方法来解决这种问题:

如果需要客户端代码从异常中恢复,则将 SQLException 转换为另一个 checked exception 。

如果客户端代码无法对其进行处理,请将 SQLException 转换为 unchecked exception 。

大多数情况下,客户端代码都是对 SQLException 无能为力的,不要犹豫,把它转换为一个 unchecked exception ,考虑以下代码:

public void dataAccessCode(){
try{
//...some code that throws SQLException
}catch(SQLException ex){
ex.printStacktrace();
}
}
这里的catch块仅仅打印异常信息而没有任何的直接操作,这样做的理由是客户端无法处理 SQLException (但是显然这种就象什么事情都没发生一样的做法是不可取的),不如通过如下的方式解决它:

public void dataAccessCode(){
try{
//...some code that throws SQLException
}catch(SQLException ex){
throw new RuntimeException(ex);
}
}
这里将 SQLException 转化为了 RuntimeException,一旦SQLException被抛出,catch块就会抛出一个RuntimeException,当前执行的线程将会停止并报告该异常。

但是,该异常并没有影响到我的业务逻辑模块,它无需进行异常处理,更何况它根本无法对SQLException进行任何操作。如果我的catch块需要根异常原因,可以使用从JDK1.4开始所有异常类中都有的getCause()方法。

如果你确信在SQLException被抛出时业务层可以执行某些恢复操作,那么你可以将其转换为一个更有意义的 unchecked exception 。但是我发现在大多时候抛出RuntimeException已经足够用了。

3. 当无法提供更加有用信息时,不要自定义异常 (Try not to create new custom exceptions if they do not have useful information for client code.)

以下代码有什么问题?

public class DuplicateUsernameException
extends Exception {}
它除了有一个“意义明确”(indicative exception)的名字以外,它没有给客户端代码提供任何有用的信息。不要忘记 Exception 跟其他的Java类一样,你可以添加你认为客户端代码将调用的方法供客户端调用,以获得有用的信息。

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

public class DuplicateUsernameException
extends Exception {
public DuplicateUsernameException
(String username){....}
public String requestedUsername(){...}
public String[] availableNames(){...}
}
新版本提供了两个有用的方法: requestedUsername(),它会返回请求的名称。availableNames(),它会返回一组与请求类似的可用的usernames。客户端可以使用这些方法来告知所请求的用户名不可用,其他用户名可用。但是如果你不准备添加这些额外的信息,那么只需抛出一个标准的Exception:

throw new Exception("Username already taken");
如果你认为客户端代码除了记录已经采用的用户名之外不会进行任何操作,那么最好抛出 unchecked exception :

throw new RuntimeException("Username already taken");
另外,你可以提供一个方法来验证该username是否被占用。

很有必要再重申一下,在客户端API可以根据异常信息进行某些操作的情况下,将使用 checked exception 。

处理程序中的错误更倾向于用 unchecked excetpion (Prefer unchecked exceptions for all programmatic errors)。它们使你的代码更具可读性。

4. 文档化异常 (Document exceptions)

你可以使用 Javadoc 的 @throws 标签来说明(document)你的API中要抛出 checked exception 或者 unchecked exception。然而,我更倾向于使用来单元测试来文档化异常(document exception)。单元测试允许我在使用中查看异常,并且作为一个可以被执行的文档来使用。不管你采用哪种方式,你要让客户端代码知道你的API中所要抛出的异常。这是一个用单元测试来测试IndexOutOfBoundsException的例子: 这里提供了IndexOutOfBoundsException的单元测试。

public void testIndexOutOfBoundsException() {
ArrayList blankList = new ArrayList();
try {
blankList.get(10);
fail("Should raise an IndexOutOfBoundsException");
} catch (IndexOutOfBoundsException success) {}
}
上面这段代码在调用 blankList.get(10) 应当抛出 IndexOutOfBoundsException 。如果没有抛出该异常,则会执行 fail("Should raise an IndexOutOfBoundsException") 显式的说明该测试失败了。通过为异常编写单元测试,你不仅可以记录异常如何触发,还可以使你的代码在经过这些测试后更加健壮。

使用异常的最佳实践 (Best Practices for Using Exceptions)

下一组最佳实践展示了客户端代码应如何处理抛出 checked exception 的API。

1. 总是要做一些清理工作 (Always clean up after yourself)

如果你在使用如数据库连接或是网络连接之类的资源,请记住要做一些清理工作 (如关闭数据库连接或者网络连接),如果你调用的API仅抛出 Unchecked exception ,你应该在使用后用try - finally块清理资源。

public void dataAccessCode(){
Connection conn = null;
try{
conn = getConnection();
//...some code that throws SQLException
}catch(SQLException ex){
ex.printStacktrace();
} finally{
DBUtil.closeConnection(conn);
}
}
class DBUtil{
public static void closeConnection
(Connection conn){
try{
conn.close();
} catch(SQLException ex){
logger.error("Cannot close connection");
throw new RuntimeException(ex);
}
}
}

DBUtil 类关闭 Connection 连接,这里的重点在于 finally 块,不管程序是否碰到异常,它都会被执行。在上边的例子中,在 finally 中关闭连接,如果在关闭连接的时候出现错误就抛出 RuntimeException 。

2. 不要使用异常来控制流程 (Never use exceptions for flow control)

生成堆栈跟踪 (stack trace) 的代价很昂贵,堆栈跟踪的价值在于debug中使用。在一个流程控制中,堆栈跟踪应当被忽视,因为客户端只想知道如何进行。

在下面的代码中,MaximumCountReachedException 被用来进行流程控制:

public void useExceptionsForFlowControl() {
try {
while (true) {
increaseCount();
}
} catch (MaximumCountReachedException ex) {
}
//Continue execution
}
public void increaseCount()
throws MaximumCountReachedException {
if (count >= 5000)
throw new MaximumCountReachedException();
}

useExceptionsForFlowControl() 用一个无限循环来增加count直到抛出异常,这种方式使得代码难以阅读,而且影响代码性能。只在要会抛出异常的地方进行异常处理。

3. 不要忽略异常 (Do not suppress or ignore exceptions)

当API中的方法抛出 checked exception 时,它在提醒你应当采取一些措施。如果 checked exception 没有任何意义,请毫不犹豫的将其转化为 unchecked exception 再重新抛出。而不是用一个空的 catch 块捕捉来忽略它,然后继续执行,以至于从表面来看仿佛什么也没有发生一样。

4. 不要捕获顶层的Exception (Do not catch top-level exceptions)

unchecked exception 都是 RuntimeException 的子类,而 RuntimeException 又继承自 Exception,如果单纯的捕获 Exception , 那么你同样也捕获了 RuntimeException ,如以下代码所示:

try{
// ...
}catch(Exception ex){
}
上边的代码(注意catch块是空的)将忽略所有的异常,包括 unchecked exception .

5. 只记录异常一次 (Log exceptions just once)

将相同的异常多次记入日志会使得检查追踪栈的开发人员感到困惑,不知道何处是报错的根源。所以只记录一次。

————————————————

原文链接:https://blog.csdn.net/zhaohuodian/article/details/126210150

 

导读异常处理是书写强健Java应用的一个重要部分,Java许你创建新的异常,并通过使用 throw 和 throws关键字抛出它们。

 http://www.searchsoa.com.cn/showcontent_71960.htm

异常处理是书写强健Java应用的一个重要部分,它是关乎每个应用的一个非功能性需求,是为了优雅的处理任何错误状况,比如资源不可访问,非法输入,空输入等等。Java提供了几个异常处理特性,以try,catch和 finally 关键字的形式内建于语言自身之中。Java编程语言也允许你创建新的异常,并通过使用 throw 和 throws关键字抛出它们。

事实上,异常处理不仅仅是知道语法,书写一个强健的代码更多的是一门艺术而不仅仅是一门科学,这里我们将讨论一些关于异常处理的Java最佳实践。这些 Java 最佳实践甚至被标准JDK库,以及一些开源代码所追随,也成为了Java程序员书写强健代码的手册。

  Java 编程中异常处理的最佳实践

  这里是我收集的10个Java编程中进行异常处理的最佳实践。在Java编程中对于检查异常有褒有贬,强制处理异常是一门语言的功能。在本文中,将尽量减少使用checked exception,同时学会在Java编程中使用checked exception VS unchecked exception

  1.为可恢复的错误使用checked exception,为编程错误使用unchecked exception

  选择checked exception还是unchecked exception,对于Java编程人员来说,总是让人感到困惑。checked exception常保证你对错误条件提供异常处理代码,这是一种从语言到强制你编写健壮的代码的一种方式,但同时会引入大量杂乱的代码并导致其不可读。当然,如果你有替代品和恢复策略的话,捕捉异常并做些什么看起来似乎也在理。

  2.在finally程序块中关闭或者释放资源

  这在Java编程中,是一个广为人知的最佳实践,在处理网络和IO类的时候,相当于一个标准:在finally块中关闭资源。

  在正常和异常执行的情况下,保证之前和稀缺资源的合理释放,这由finally块保证。从Java7开始,该语言有了一项更有趣的功能:资源管理自动化或者ARM块能实现这一功能。尽管如此,我们仍然要记住在finally块中关闭资源,这是对于释放像FileDescriptors这类,应用在socket和文件编程的情况下的有限资源很重要的。

  3.在堆栈跟踪中包含引起异常的原因

  很多时候,当一个由另一个异常导致的异常被抛出的时候,Java库和开放源代码会将一种异常包装成另一种异常,日志记录和打印根异常就变得非常重要。Java异常类提供了 getCause()方法来检索导致异常的原因,这些(原因)可以对异常的根层次的原因提供更多的信息。

  该Java实践对在进行调试或排除故障大有帮助,时刻记住,如果你将一个异常包装成另一种异常时,构造一个新异常一定要传递源异常。

  4.始终提供关于异常的有意义的完整的信息

  异常信息是最重要的地方,因为这是程序员首先看到的第一个地方,这里你能找到问题产生的根本原因。这里始终提供精确的真实的信息。例如,对比IllegalArgumentException 异常的两条异常信息:

  消息 1: "Incorrect argument for method"

  消息 2: "Illegal value for ${argument}: ${value}

  第一条消息仅说明了参数是非法的或者不正确,但第二条消息包括了参数名和非法值,而这对于找到错误的原因是很重要的。

  5.避免过度使用checked exception

  checked exception在强制执行方面有一定的优势,但同时它也破坏了代码,通过掩盖业务逻辑使代码可读性降低。只要你不过度使用checked exception,你可以最大限度的减少这类情况,这样做的结果是你会得到更清洁的代码。你同样可以使用Java7的新功能,像one catch block for multiple exceptions 和 automatic resource management以移除重复项。

  6.将checked exception转为runtime exception

  这是在像Spring之类的多数框架中用来限制使用checked exception的技术之一,大部分出自于JDBC的checked exception,都被包装进DataAccessException中,而(DataAccessException)异常是一种unchecked exception

  这是Java最佳实践带来的好处,特定的异常限制到特定的模块,像 SQLException 放到DAO层,将意思明确的运行时异常抛到客户层。

  7.记住对性能而言,异常代价高昂

  需要记住的一件事是异常代价高昂,同时让你的代码运行缓慢。假如你有方法从ResultSet(结果集)中进行读取,这时常会抛出SQLException异常而不会移到下一元素,这将会比不抛出异常的正常代码执行的慢的多。因此最大限度的减少不必要的异常捕捉和移动,那里没有什么固定的原因。不要仅仅是抛出和捕捉异常,如果你能使用boolean变量去表示执行结果,可能会得到更整洁,更高性能的解决方案。修正错误的根源,避免不必须要的异常捕捉。

  8.避免catch块为空

  没有什么比空的catch块更糟糕的了,因为它不仅隐藏了错误和异常,同时可能导致你的对象处于不可使用或者脏的状态。空的catch块只能变得无意义,如果你非常肯定异常不会继续以任何方式影响对象状态,但在程序执行期间,用日志记录错误依然是最好的(方法)。

  对于在Java编程中编写异常处理代码,这不仅仅是一个Java最佳实践,而是一个最通用的实践。

  9.使用Jdk标准异常

  我们的第九条最佳实践建议使用标准和内置的Java异常。使用标准异常而不是每次创建我们自己的异常,对于维护性和一致性,不管是现在还是以后,都是最好的选择。重用标准异常使代码更具可读性,因为大部分Java开发人员对标准的像源自于JDK的RuntimeException 异常,IllegalStateException 异常,IllegalArgumentException 异常或者 NullPointerException异常,(开发者)他们能一眼就知道每种异常的目的,而不是在代码里查找或者在文档里查找用户定义的异常的目的。

  10.Javadoc记录异常

  Java提供了throw和throws关键字来抛出异常,在javadoc中用 @throw记录任何方法可能会抛出的异常。如果你编写API或者公共接口,这就变得非常重要。任何方法抛出的异常都有相应的文档记录,这样你就能下意识的提醒任何使用(该方法)的人。

  这些就是所有在Java编程中在处理异常的时候需要遵循的最佳实践。让我们知道了什么是在Java编程中编写异常处理代码时需要遵循的实践。

 

标签:exception,Java,抛出,实践,最佳,checked,异常,客户端
From: https://www.cnblogs.com/erichi101/p/18186208

相关文章

  • Java拦截器(Interceptor)详细解析
    转载链接地址:https://www.cnblogs.com/xiaoyh/p/16444681.html一、拦截器概念讲解拦截器的概念之前,我们先看一张图: (1)浏览器发送一个请求会先到Tomcat的web服务器(2)Tomcat服务器接收到请求以后,会去判断请求的是静态资源还是动态资源(3)如果是静态资源,会直接到Tomcat......
  • 基于spring boot Java的数组转树状结构类
    基于springboot的数组转树状结构类importjava.lang.reflect.Method;importjava.util.ArrayList;importjava.util.List;importjava.util.function.Function;publicclassArray{private<E,V>VcreateViewObject(Eentity,Class<V>viewObjectClass)thro......
  • java常用加密手段
    虽然一般都会给APK上壳。但是拖壳还是要分析JAVa链接:https://pan.baidu.com/s/1Qm0cF0u7RCGy174QYeVf2Q?pwd=c60g提取码:c60g1.隐藏字节-将明文字符串->转为字节定义publicstaticvoidmain(String[]args)throwsIOException{Stringstrq=newString(newbyte[]{97,......
  • 第一个JAVA代码
    第一个JAVA代码创建存放代码的文件夹在合适的路径中新建存放代码的文件夹。新建一个Java文件1.新建一个文件2.文件名为HelloWorld3.将后缀改为.java输入代码使用notepad++打开新建的文件,输入以下代码并保存publicclassHelloWorld{ publicstaticvoidmain(String[]a......
  • Java面试题:Spring Bean线程安全?别担心,只要你不写并发代码就好了!
    Spring中的Bean是否线程安全取决于Bean的作用域(scope)。Spring提供了几种不同的Scope,其中包括Singleton、Prototype、Request、Session、GlobalSession等。 SingletonScope(单例模式)默认情况下,SpringBean是SingletonScope,这意味着在整个应用程序上下文中只有一个实例。......
  • HBase Meta 元信息表修复实践
    作者:vivo互联网大数据团队-HuangGuihu、ChenShengzunHBase是一款开源高可靠、高可扩展性、高性能的分布式非关系型数据库,广泛应用于大数据处理、实时计算、数据存储和检索等领域。在分布式集群中,硬件故障是一种常态,硬件故障可能导致节点或者集群级别服务中断、meta表损坏......
  • 一文看懂!高科技企业如何选择最佳的替代FTP的方案
    高科技企业,也即我们熟知的高新技术企业,是指那些知识密集和技术密集的经济实体。这些企业普遍具有创新性强、技术领先、科研投入高、成长高速等特点。而由于企业的发展迅速,内部的数据产生和交互也极为频繁,文件的流转场景和业务扩展速度成正相关,因此,保证企业的文件和数据安全、高速......
  • JAVA下载安装配置基础
    JAVA下载安装配置基础JDK、JRE、JVMJDK:JavaDevelopmentKitJava开发者工具,他包含JRE和JVMJRE:JavaRuntimeEnvironmentJava运行时环境包含JVMJVM:JAVAVirtualMachineJave虚拟机下载配置JDK搜索下载JDK8(目前使用率较高的版本)安装JDK记住安装的路径进入高......
  • 【MySQL】求和查询,目标值int,但空数据时返回null的问题(Java)
    问题分析intselectDeviceMonthRepairCount(StringdeviceType,Stringmonth);<selectid="selectDeviceMonthRepairCount"resultType="int">SELECTSUM(repair_count)FROMwarranty_recordsWHEREdevice_type=......
  • java代码规范
    Java代码规范规范类型规范要求命名规范-类名:大写字母开头,驼峰命名法。<br>-方法名、变量名、包名:小写字母开头,驼峰命名法。<br>-常量:全大写字母,下划线分隔单词。缩进和空格-使用4个空格进行缩进,不使用制表符。<br>-二元运算符前后应该加上空格,以增......