当前位置: 首页 > 科技观察

Java异常处理的20个实践,你知道几个?

时间:2023-03-15 00:17:58 科技观察

异常处理是Java开发的重要组成部分,用于处理任何错误情况,如资源不可访问、无效输入、空输入等。Java以try、catch和最后关键字。Java编程语言还允许创建新的自定义异常并使用throw和throws关键字抛出它们。在Java编程中,Java异常处理不仅仅是知道语法那么简单,它必须遵循标准的JDK库和开源代码来处理错误和异常。在这里,我们将讨论有关异常处理的一些Java最佳实践。在我们讨论异常处理的最佳实践之前,让我们了解下几个重要的概念,即什么是异常以及异常的分类。什么是例外?异常的英文单词是exception,异常本质上是程序错误,包括程序逻辑错误和系统错误。比如使用空引用、数组下标越界、内存溢出错误等,这些都是意想不到的情况,偏离了我们程序本身的本意。错误经常发生在编写程序的过程中,包括编译和运行时的错误。编译器帮助我们纠正编译时出现的错误,但是运行时的错误超出了编译器的能力范围,而且运行时的错误往往是不可预测的。如果程序在运行过程中出现错误,如果置之不理,程序就会终止或者直接导致系统崩溃。显然这不是我们希望看到的结果。运行过程中出现的错误如何处理和补救?Java提供了异常机制,通过异常机制来处理程序运行过程中发生的错误。通过异常机制,我们可以更好的提高程序的健壮性。异常分类Java将异常作为对象来处理,定义了一个基类java.lang.Throwable作为所有异常的超类。Java包括三种类型的异常:已检查异常、未检查异常和错误。检查异常是必须在方法的throws子句中声明的异常。它们扩展了Exception并且旨在成为一种“在你面前”的异常类型。JAVA希望您能够处理它们,因为它们在某种程度上取决于程序之外的外部因素。已检查异常表示正常系统运行期间可能发生的预期问题。当您尝试通过网络或文件系统使用外部系统时,通常会发生这些异常。在大多数情况下,对已检查异常的正确响应应该是稍后重试,或者提示用户修改他们的输入。UncheckedExceptions是不需要在throws子句中声明的异常。由于程序错误,JVM不会强制您处理它们,因为它们中的大多数是在运行时产生的。它们扩展了RuntimeException。最常见的例子是NullPointerException,未经检查的异常可能不应该重试,正确的操作通常应该是什么都不做,让它离开你的方法和执行堆栈。错误是严重的运行时环境问题,绝对无法从中恢复。示例包括OutOfMemoryError、LinkageError和StackOverflowError,它们通常会使程序崩溃。所有不属于RuntimeExceptions的异常统称为CheckedExceptions,也称为checkedexceptions。这种异常的发生不是程序本身的问题,通常是外部因素引起的。为了防止这些异常打断程序或者得到不正确的结果,Java要求在编写可能产生此类异常的程序代码时,必须进行异常处理。Java语言将所有从RuntimeException类或Error类派生的异常称为未检查异常。Java异常层次图如下图所示:了解了异常的基本概念和分类之后,现在让我们开始探索异常处理的最佳实践。异常处理最佳实践不要忽略捕获到的异常捕获异常但什么也不做,除非您确定异常可以被忽略。这样,外界就无法知道方法发生了错误,也无法确定定位错误的原因。在你的方法中抛出定义的特定检查异常必须避免上面的代码示例,它违背了检查异常的目的。声明你的方法可能抛出的具体检查异常,如果这种检查异常太多,你应该将它们包装在你自己的异常中,并在异常消息中添加信息。如果可能,您还可以考虑代码重构。捕获具体子类而不是捕获Exception的问题在于,如果稍后调用的方法在其方法声明中添加了新的已检查异常,开发人员的意图是应该处理具体的新异常。如果您的代码只是捕获异常(或Throwable),它永远不会知道此更改,并且您的代码现在是错误的,并且可能在运行时的任何时候中断。永远不要捕获Throwable类这是一个更严重的麻烦,因为JavaError也是Throwable的子类,Error是JVM本身无法处理的不可逆条件,对于某些JVM实现,JVM实际上甚至可能捕获不到ErrorCallcatch子句。始终在自定义异常中正确包装异常,以便堆栈跟踪不会丢失这会破坏原始异常的堆栈跟踪,并且总是错误的,正确的做法是:记录异常或抛出异常,但不要这样做如上我的代码中,logging和throwingexceptions在日志文件中产生了多条日志消息,代码中出现了单一的问题,对于试图分析日志的同事来说不是很友好。永远不要在finally块中抛出任何异常只要cleanUp()从不抛出任何异常,上面的代码就没问题,但是如果someMethod()抛出异常,而在finally块中,cleanUp()也抛出另外一个异常,那么程序只会抛出第二个异常,而原来的第一个异常(正确的原因)将永远丢失。如果在finally块中调用的代码可能会抛出异常,请确保处理它或记录它。永远不要让它从finally块中抛出。Alwaysonlyonlycatchexceptionsthatcanactuallyhandle这是最重要的概念,不要为了捕捉异常而捕捉,只有当你想处理异常时才捕捉异常,或者想在那个异常中提供额外的上下文信息。如果你不能在catch块中处理它,最好的建议是不要抓住它只是为了重新抛出它。不要使用printStackTrace()语句或类似语句。当您完成代码时,永远不要忽略printStackTrace(),其他人可能最终得到堆栈并且完全不知道如何处理它,因为它没有附加任何上下文信息。对于不打算处理的异常,直接使用finally是一个很好的做法。如果在你的方法中你访问的是方法2,而方法2抛出了一些你不想在方法1中处理的异常,但还是想在异常发生时做一些清理,然后在finally块中做,不要使用捕获块。记住早抛晚捕获原则这大概是关于异常处理最著名的原则了,简单来说就是异常应该尽快抛出(throw),尽可能晚地捕获(catch)。应该等到有足够的信息才能妥善处理。隐含在这个原则中,您更有可能将它放在一个低级方法中,您将在其中检查单个值是否为null或不合适。并且您让异常堆栈跟踪上升了几个级别,直到您达到足够的抽象级别来处理问题。异常处理后清理资源如果您正在使用数据库连接或网络连接等资源,请确保清理它们。如果您调用的API仅使用未经检查的异常,您仍应使用try-finally块来清理资源。在try模块中访问资源,最后在finally中关闭资源。即使在访问资源时出现任何异常,资源也会被优雅地关闭。仅抛出与方法相关的异常依赖项对于保持应用程序清洁很重要。一种尝试读取文件的方法,如果抛出NullPointerException,则不会向用户提供任何相关信息。相反,如果将此类异常包装在自定义异常中会更好。NoSuchFileFoundException对该方法的用户更有用。永远不要在你的程序中使用异常来控制流程不要在你的项目中使用异常来处理应用程序逻辑。永远不要这样做,它会使代码难以阅读和理解。尽早验证用户输入以在请求处理中尽早捕获异常始终在非常早的阶段验证用户输入,甚至在到达控制器之前,这将帮助您最大限度地减少核心应用程序逻辑中的异常处理代码量。在用户输入错误的情况下,它也保证与应用程序一致。示例:如果在用户注册应用程序中,遵循以下逻辑:验证用户插入用户验证地址插入地址如果出现问题回滚一切这不是正确的做法,它会在各种情况下使数据库处于不一致的状态,应该首先验证一切,然后将用户数据放入dao层并进行数据库更新。正确的做法是:验证用户验证地址插入用户插入地址如果有问题回滚一切异常只能包含在一个日志中不要像上面那样做,使用多行日志消息进行多个LOGGER.debug()调用可能在你的测试用例中看起来很好,但是当它出现在一个有100个线程并行运行的应用服务器的日志文件中时,所有的信息都输出到同一个日志文件中,尽管它们是前线和后线在真实的代码中,但是日志文件中的两条日志消息可能相隔100多行。应该这样做:将所有相关信息尽可能多地传递给异常。有用的异常消息和堆栈跟踪非常重要。如果你的日志找不到异常位置,那日志有什么用呢?终止被中断的线程InterruptedException异常提示应该停止程序正在做的事情,比如事务超时或者线程池被关闭等,而不是忽略InterruptedException,你应该尽力完成你正在做的事情,并完成当前正在执行的线。修改后的程序如下:对于重复的try-catch,代码中有很多类似的catch块,使用模板方法是没有用的,只会增加代码的重复,这种情况下可以使用模板方法问题。例如,尝试关闭数据库连接时的异常处理。这类方法在应用程序中会用到很多地方。不要把这段代码到处乱扔,定义上面的方法并像这样使用它:使用JavaDoc记录应用程序中的所有异常还尝试包括用户应遵循的操作,以防止这些异常发生。综上所述,本文首先介绍了什么是异常,以及异常的三种分类,然后通过20个最佳实践来讨论如何处理异常,希望能在以后提高和理解异常。