关于JAVA异常处理的20个最佳实践

关于JAVA异常处理的20个最佳实践

在我们深入了解异常处理最佳实践的深层概念之前,让我们从一个最重要的概念开始,那就是理解在JAVA中有三种一般类型的可抛类: 检查性异常(checked exceptions)、非检查性异常(unchecked Exceptions) 和 错误(errors)。

异常类型

图片描述

检查性异常(checked exceptions) 是必须在在方法的throws子句中声明的异常。它们扩展了异常,旨在成为一种“在你面前”的异常类型。JAVA希望你能够处理它们,因为它们以某种方式依赖于程序之外的外部因素。检查的异常表示在正常系统操作期间可能发生的预期问题。 当你尝试通过网络或文件系统使用外部系统时,通常会发生这些异常。 大多数情况下,对检查性异常的正确响应应该是稍后重试,或者提示用户修改其输入。
非检查性异常(unchecked Exceptions) 是不需要在throws子句中声明的异常。 由于程序错误,JVM并不会强制你处理它们,因为它们大多数是在运行时生成的。 它们扩展了RuntimeException。 最常见的例子是NullPointerException [相当可怕..是不是?]。 未经检查的异常可能不应该重试,正确的操作通常应该是什么都不做,并让它从你的方法和执行堆栈中出来。 在高层次的执行中,应该记录这种类型的异常。
错误(errors) 是严重的运行时环境问题,几乎肯定无法恢复。 例如OutOfMemoryErrorLinkageErrorStackOverflowError, 它们通常会让程序崩溃或程序的一部分。 只有良好的日志练习才能帮助你确定错误的确切原因。

用户自定义异常

任何时候,当用户觉得他出于某种原因想要使用自己的特定于应用程序的异常时,他可以创建一个新的类来适当的扩展超类(主要是它的Exception.java)并开始在适当的地方使用它。 这些用户定义的异常可以以两种方式使用:
1) 当应用程序出现问题时,直接抛出自定义异常

throw new DaoObjectNotFoundException("Couldn't find dao with id " + id);

2) 或者将自定义异常中的原始异常包装并抛出

catch (NoSuchMethodException e) {throw new DaoObjectNotFoundException("Couldn't find dao with id " + id, e);
}

包装异常可以通过添加自己的消息/上下文信息来为用户提供额外信息,同时仍保留原始异常的堆栈跟踪和消息。 它还允许你隐藏代码的实现细节,这是封装异常的最重要原因。

现在让我们开始探索遵循行业聪明的异常处理的最佳实践。

你必须考虑并遵循的最佳做法

1) 永远不要吞下catch块中的异常

catch (NoSuchMethodException e) {return null;
}

2) 在你的方法里抛出定义具体的检查性异常

public void foo() throws Exception { //错误方式
}

一定要避免出现上面的代码示例。 它简单地破坏了检查性异常的整个目的。 声明你的方法可能抛出的具体检查性异常。 如果只有太多这样的检查性异常,你应该把它们包装在你自己的异常中,并在异常消息中添加信息。 如果可能的话,你也可以考虑代码重构。

public void foo() throws SpecificException1, SpecificException2 { //正确方式
}

3) 捕获具体的子类而不是捕获Exception类

try {someMethod();
} catch (Exception e) { //错误方式LOGGER.error("method has failed", e);
}

捕获异常的问题是,如果稍后调用的方法为其方法声明添加了新的检查性异常,则开发人员的意图是应该处理具体的新异常。 如果你的代码只是捕获异常(或Throwable),你永远不会知道这个变化,以及你的代码现在是错误的,并且可能会在运行时的任何时候中断。

4) 永远不要捕获Throwable类

这是一个更严重的麻烦。 因为java错误也是Throwable的子类。 错误是JVM本身无法处理的不可逆转的条件。 对于某些JVM的实现,JVM可能实际上甚至不会在错误上调用catch子句。

5) 始终正确包装自定义异常中的异常,以便堆栈跟踪不会丢失

catch (NoSuchMethodException e) {throw new MyServiceException("Some information: " + e.getMessage());  //错误方式
}

这破坏了原始异常的堆栈跟踪,并且始终是错误的。 正确的做法是:

catch (NoSuchMethodException e) {throw new MyServiceException("Some information: " , e);  //正确方式
}

6) 要么记录异常要么抛出异常,但不要一起执行

catch (NoSuchMethodException e) {  
//错误方式 LOGGER.error("Some information", e);throw e;
}

正如在上面的示例代码中,记录和抛出异常会在日志文件中产生多条日志消息,代码中存在单个问题,并且让尝试挖掘日志的工程师生活得很糟糕。

7) finally块中永远不要抛出任何异常

try {someMethod();  //Throws exceptionOne
} finally {cleanUp();    //如果finally还抛出异常,那么exceptionOne将永远丢失
}

只要cleanUp()永远不会抛出任何异常,上面的代码没有问题。 但是如果someMethod()抛出一个异常,并且在finally块中,cleanUp()也抛出另一个异常,那么程序只会把第二个异常抛出来,原来的第一个异常(正确的原因)将永远丢失。 如果你在finally块中调用的代码可能会引发异常,请确保你要么处理它,要么将其记录下来。 永远不要让它从finally块中抛出来。

8) 始终只捕获实际可处理的异常

catch (NoSuchMethodException e) {throw e; //避免这种情况,因为它没有任何帮助
}

这是最重要的概念。 不要为了捕捉异常而捕捉,只有在想要处理异常时才捕捉异常,或者希望在该异常中提供其他上下文信息。 如果你不能在catch块中处理它,那么最好的建议就是不要只为了重新抛出它而捕获它。

9) 不要使用printStackTrace()语句或类似的方法

完成代码后,切勿忽略printStackTrace()。 你的同事可能会最终得到这些堆栈,并且对于如何处理它完全没有任何知识,因为它不会附加任何上下文信息。

10) 如果你不打算处理异常,请使用finally块而不是catch块

try {someMethod();  //Method 2
} finally {cleanUp();    //do cleanup here
}

这也是一个很好的做法。 如果在你的方法中你正在访问Method 2,而Method 2抛出一些你不想在method 1中处理的异常,但是仍然希望在发生异常时进行一些清理,然后在finally块中进行清理。 不要使用catch块。

11) 记住“早throw晚catch”原则

这可能是关于异常处理最著名的原则。 它基本上说,你应该尽快抛出(throw)异常,并尽可能晚地捕获(catch)它。 你应该等到你有足够的信息来妥善处理它。

这个原则隐含地说,你将更有可能把它放在低级方法中,在那里你将检查单个值是否为空或不适合。 而且你会让异常堆栈跟踪上升好几个级别,直到达到足够的抽象级别才能处理问题。

12) 清理总是在处理异常之后

如果你正在使用数据库连接或网络连接等资源,请确保清除它们。 如果你正在调用的API仅使用非检查性异常,则仍应使用try-finally块来清理资源。 在try模块里面访问资源,在finally里面最后关闭资源。 即使在访问资源时发生任何异常,资源也会优雅地关闭。

13) 只从方法中抛出相关异常

相关性对于保持应用程序清洁非常重要。 一种尝试读取文件的方法; 如果抛出NullPointerException,那么它不会给用户任何相关的信息。 相反,如果这种异常被包裹在自定义异常中,则会更好。 NoSuchFileFoundException则对该方法的用户更有用。

14) 切勿在程序中使用流程控制异常

我们已经阅读过很多次,但有时我们还是会在项目中看到开发人员尝试为应用程序逻辑而使用异常的代码。 永远不要这样做。 它使代码很难阅读,理解和丑陋。

15) 验证用户输入以在请求处理的早期捕获不利条件

始终要在非常早的阶段验证用户输入,甚至在达到实际controller之前。 它将帮助你把核心应用程序逻辑中的异常处理代码量降到最低。 如果用户输入出现错误,它还可以帮助你使与应用程序保持一致。

例如:如果在用户注册应用程序中,你遵循以下逻辑:

1)验证用户
2)插入用户
3)验证地址
4)插入地址
5)如果出问题回滚一切

这是非常不正确的做法。 它会使数据库在各种情况下处于不一致的状态。 首先验证所有内容,然后将用户数据置于dao层并进行数据库更新。 正确的做法是:

1)验证用户
2)验证地址
3)插入用户
4)插入地址
5)如果问题回滚一切

16) 一个异常只能包含在一个日志中

LOGGER.debug("Using cache sector A");
LOGGER.debug("Using retry sector B");

不要这样做。

对多个LOGGER.debug()调用使用多行日志消息可能在你的测试用例中看起来不错,但是当它在具有400个并行运行的线程的应用程序服务器的日志文件中显示时,所有转储信息都是相同的日志文件,你的两个日志消息最终可能会在日志文件中间隔1000行,即使它们出现在代码的后续行中。

像这样做:

LOGGER.debug("Using cache sector A, using retry sector B");

17) 将所有相关信息尽可能地传递给异常

有用且信息丰富的异常消息和堆栈跟踪也非常重要。 如果你的日志不能确定任何事情(有效内容不全或很难确定问题原因),
那要日志有什么用? 这类的日志只是你代码中的装饰品。

18) 终止掉被中断线程

while (true) {try {Thread.sleep(100000);} catch (InterruptedException e) {} //别这样做doSomethingCool();
}

InterruptedException是你的代码的一个提示,它应该停止它正在做的事情。 线程中断的一些常见用例是active事务超时或线程池关闭。 你的代码应该尽最大努力完成它正在做的事情,并且完成当前的执行线程,而不是忽略InterruptedException。 所以要纠正上面的例子:

while (true) {try {Thread.sleep(100000);} catch (InterruptedException e) {break;}
}
doSomethingCool();

19) 使用模板方法重复try-catch

在你的代码中有100个类似的catch块是没有用的。 它增加代码的重复性而且没有任何的帮助。 对这种情况要使用模板方法。

例如,下面的代码尝试关闭数据库连接。

class DBUtil{public static void closeConnection(Connection conn){try{conn.close();} catch(Exception ex){//Log Exception - Cannot close connection}}
}

这种类型的方法将在你的应用程序的成千上万个地方使用。 不要把这块代码放的到处都是,而是定义顶层的方法,并在下层的任何地方使用它:

public void dataAccessCode() {Connection conn = null;try{conn = getConnection();....} finally{DBUtil.closeConnection(conn);}
}

20) 在JavaDoc中记录应用程序中的所有异常

把注释(javadoc)运行时可能抛出的所有异常作为一种习惯。
也要尽可能包括可行的方案,用户应该关注这些异常发生的情况。

这就是我现在所想的。 如果你发现任何遗漏或你与我的观点不一致,请发表评论。 我会很乐意讨论。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/283132.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SSM框架搭建(四) springmvc和mybatis的配置

SSM框架搭建(一) JDK和MAVEN环境搭建 SSM框架搭建(二) 创建MAVEN项目 SSM框架搭建(三) 数据库创建和MyBatis生成器自动生成实体类、DAO接口和Mapping映射文件 SSM框架搭建(四) sprin…

10 个有关 String 的面试问题

2019独角兽企业重金招聘Python工程师标准>>> 下面是面试中最容易问到的有关String的问题。 1. 如何比较两个字符串?使用“”还是equals()方法? 简单来讲,“”测试的是两个对象的引用是否相同,而equals()比较的是两个字…

基于ASP.NET Core 6.0的整洁架构

背景最近尝试录制了一个系列视频:《ASP.NET Core 6.0Vue.js 3 实战开发》,本节是视频内部整洁架构的理论和实战的文字稿。因为在录制之前,我通常会编写完整的文字内容作为视频文案,这里分享给大家,希望对你有所帮助。如…

大并发数据队列

图解#include<stdio.h>#include<stdlib.h>#include<memory.h>#define N 100#define mytype intstruct MyQueue{mytype data[N];//数组存储队列int front;//拉屎int rear;//吃东西};typedef struct MyQueue myQ;//初始化void init(myQ *p){p->front p->…

总在用户态调试 C# 程序,终还是搭了一个内核态环境

一&#xff1a;背景 一直在用 WinDbg 调试用户态程序&#xff0c;并没有用它调试过 内核态&#xff0c;毕竟不是做驱动开发&#xff0c;也没有在分析 dump 中需要接触用内核态的需求&#xff0c;但未知的事情总觉得很酷&#xff0c;加上最近在看 《深入解析 Windows 操作系统》…

bat kafka启动_windows下搭建Kafka,并通过命令窗口收发消息

参考网址&#xff1a;前提条件&#xff1a;windows环境需要安装jdk2.由于Kafka依赖于zookeeper&#xff0c;所以也需要下载zookeeper,可以通过官网下载http://zookeeper.apache.org/3.安装zookeeper将压缩包解压后&#xff0c;到bin目录下&#xff0c;启动zkServer.bat即可注意…

异常处理、socke基于TCP协议编程

一、异常处理 1、错误和异常 1.程序中难免出现错误&#xff0c;而错误分成两种 &#xff08;1&#xff09;语法错误&#xff08;这种错误过不了Python解释器的语法检测&#xff0c;必须在程序执行前改正&#xff09; #语法错误示范一 if#语法错误示范二 def test:pass#语法错误…

windows下apache报错The requested operation has failed解决方法

2019独角兽企业重金招聘Python工程师标准>>> Apache报错The requested operation has failed&#xff0c;基本上是因为端口被占用。解决方法如下&#xff1a; 第一步&#xff0c;运行cmd&#xff0c;cd 定位到Apache安装目录的bin目录下&#xff0c;输入httpd.exe -…

stm32 usmart使用

我直接用正点原子给的&#xff0c;步骤如下 先添加三个.c进工程&#xff0c;添加两个头文件的编译路径 #include "usart.h"#include "usmart.h" main函数里添加如下 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);// 设置中断优先级分组2 uart_init(960…

Istio 1.15 发布,支持 arm64 架构处理器

Istio 是基于容器的云原生技术栈的三大核心技术之一&#xff0c;另外两个是 Kubernetes 和 Knative。其中 Kubernetes 和 Knative 早已支持了 arm64 架构&#xff0c;甚至连 Istio 的数据平面 Envoy 早在 1.16 版本 [1] 就已支持 arm64 架构&#xff08;2020 年 10 月&#xff…

TP框架表单验证 【包含ajax方法】

之前的表单验证都是用js写的&#xff0c;这里也可以使用tp框架的验证。但是两者比较而言还是js验证比较好&#xff0c;因为tp框架验证会运行后台代码&#xff0c;这样运行速度和效率就会下降。  自动验证是ThinkPHP模型层提供的一种数据验证方法&#xff0c;可以在使用create创…

Spring 入门学习二之IOC

今天来学习Spring ioc .一、spring jar 包导入 在 spring 官网下载开发包 spring-framework-4.2.4.RELEASE,然后导入需要的 jar 包到项目 /lib/ 目录下。 &#xfffc; 二、代码开发 新建一个 src/cn/sxt/bean/Hello.java文件 package cn.sxt.bean;/*** Created by kaiyiwang o…

java 物理内存_聊聊Java中的内存

JVM的内存先放一张JVM的内存划分图&#xff0c;总体上可以分为堆和非堆(粗略划分&#xff0c;基于java8)那么一个Java进程最大占用的物理内存为&#xff1a;Max Memory eden survivor old String Constant Pool Code cache compressed class space Metaspace Thread st…

.Net CoreRabbitMQ基本使用

队列模式https://www.rabbitmq.com/getstarted.html对以上几种模式进行简要分类&#xff0c;可以分成如下三类(RPC暂不考虑)简单队列模式&#xff0c;单发单收&#xff0c;一对一模式Worker模式&#xff0c;单发多收(一个消息一个接收者&#xff0c;多个消息多个接收者)&#x…

Linux包系列的知识(附:Ubuntu16.04升级到18.04的案例)

Linux基础&#xff1a;https://www.cnblogs.com/dunitian/p/4822808.html#linux 之前看到朋友还动不动 apt-get update upgrade&#xff0c;就很纳闷&#xff0c;后来发现原来他只是知道这个更新命令却不知其意&#xff0c;所以每次安装个包就把所有apt-get的常用清除更新命令打…

java获取tomcat目录结构_Tomcat目录结构详解

Tomcat目录结构图如下&#xff1a;bin目录存放一些可执行的二进制文件&#xff0c;.sh结尾的为linux下执行命令&#xff0c;.bat结尾的为windows下执行命令。catalina.sh&#xff1a;真正启动tomcat文件&#xff0c;可以在里面设置jvm参数。startup.sh&#xff1a;启动tomcat(需…

智慧农业物联网云平台方案

2019独角兽企业重金招聘Python工程师标准>>> 多比智慧农业物联网云平台解决方案结合了最先进的物联网、云计算、传感器、自动控制等, 在浏览器或手机客户端实时显示大棚、大田、温室等温度、湿度、PH值、光强度、CO2&#xff0c;或作为自动控制的参变量参与到自动控…

使用JDBC获取Oracle连接时报错

The Network Adapter could not establish the connection 网络适配器不能创建连接 作为初学者的来说&#xff0c;这个问题让我找了好多次&#xff0c;每次重新开启电脑时就可以正常获取连接&#xff0c;过了一会儿&#xff0c;自己不知道做了什么就会又报错&#xff0c;…

.Net CoreRabbitMQ消息转发可靠机制(上)

前言生产者发送消息到了队列&#xff0c;队列推送数据给了消费者&#xff0c;这里存在一些问题需要思考下生产者如何确保消息一定投递到了队列中RabbitMQ 丢失了消息(下文暂不涉及这块)队列如何确保消费者收到了消息呢生产者可靠发送执行流程当生产者将消息发送出去后&#xff…

一个java文件中可包含多个main方法

java中的main方法是java应用程序的入口&#xff0c;java程序在运行时&#xff0c;首先调用执行main方法。但并不是说java中只能有一个main方法&#xff0c;不同类中都可以包含main方法。当JVM进行编译时&#xff0c;会提示选择其中一个main方法作为编译的入口。 转载于:https:/…