【死磕 Java 基础】 — 你以为异常就是 try…catch ?那你天真了

2023-11-08

大家好,我是大明哥。

个人网站:https://www.cmsblogs.com/


前言

我敢说对于很多小伙伴来说,他们以为在 Java 中异常就是 try...catch,稍微有点儿意识的还会用下 throw new Exception,真的有这么简单吗?请宽恕小编直言,你对 Java 异常一知半解。以下是小编对 Java 异常的理解,如有错误之处,请原谅,资质有限。

引出异常

先问一个问题,在 Java 中,你是赞同使用返回码还是异常来规范错误呢?

我先来说说使用返回码的情况,比如登录逻辑,我们分为如下几种情况:

  • 0:登录成功
  • 1:用户名错误,没有该用户
  • 2:用户密码输入错误
  • 3:第一次登录,重置密码
  • 4:密码连续错 5 次,冻结账户

使用返回码一般都是如下处理:

        int code = userService.login(userName,password);
        if (code == 0) {
            return "登录成功";
        }else if (code == 1) {
            return "用户名错误,请重新输入";
        }else if (code == 2) {
            return "密码输入错误,请重新输入";
        } else if (code == 3) {
            return "您是第一次登录,请重置密码";
        } else if (code == 4) {
            return "您密码已连续输错 5 次,冻结一天";
        }

当然,您可能有更加优雅的处理方式,比如把 code 直接返回给前端,让前端来根据返回码来展示不同的异常。如:

return userService.login(userName,password);

这种后端处理方式确实是比较优雅,但是前端呢?如果你后端再增加 5、6 种返回码,你觉得你前端的同事不会打死你吗?

那怎么解决呢?

  1. 首先约定 code 和 message,例如:0000,表示成功,0001,表示第一次登录,跳转到重置页面,-0001,表示登录失败。然后 message 描述返回信息。
  2. 后端统一异常处理。

可能有小伙伴说,这种是前后端的交互,那纯后端模式呢?一样应该使用异常。

如果你使用返回码来规范接口,如:

  • 0:成功
  • 1:异常 1
  • 2:异常 2
  • 3:异常 3

那么别人在调用的写的方法时,势必需要区分这几种情况,比如 code = 0 怎么处理,code = 1 怎么处理,…,刚刚开始他确实也是这样处理的,而且处理地非常好,你们程序运行也非常好,但是某天,你增加了一个 code = 5的,然后你恰巧忘记告诉他了,嗯,上线后你就等着挨批吧。

其实这种单返回值还好处理,如果是多返回值,请问你怎么处理?如果返回的结果是一个 List 集合呢?你是不是得要构造一个 Map 或者对象,别说你返回 null 哈,否则我要敲你脑袋了。所以这种通过返回码的处理方式还是有一些问题的:

  1. 程序员不小心忘记返回值的检查,从而造成 BUG
  2. 方法接口变得非常不纯洁了,正常值和错误值混淆在一起,导致语义问题

大明哥,在真实项目里面看到过这样处理的逻辑,当时我一看,菊花瞬间就紧了,惊为天人啊。如下

public class ServiceA {
    
    public String method1() {
        doSomething1();
        
        doSomething2();
        
        if (a) {
            return "false@xxxx";
        } else if (b) {
            return "false@zzzzz";
        } else if (c) {
            return "false@ccccc";
        } else {
            return "true@vvvvv";
        }
    }
}

调用方:

public class ServiceB {
    
    public void method2() {
        String result = method1();
        String[] results = result.split("@");
        if ("false".equals(results[0])) {
            if (results[1].contains("xxxx")) {
                doSomething1();
            }
            if (results[1].contains("zzzzz")) {
                doSomething2();
            }
            if (results[1].contains("ccccc")) {
                doSomething3();
            }
        }
    }
}

我就问你怕不怕?

在我看来通过返回码来判断程序的运行结果是否正确,真的是一件吃力不讨好的事情。Java 提供了 try...catch 这么优雅的处理方式,他难道不香吗?

可能有小伙会问,你写的方法如果抛出了异常,没有告诉其他同事,是不是也会导致代码异常?我丢,如果这个异常需要你同事做额外处理,你不会抛出 checked Exception 么?

可能又有小伙伴说,抛出异常会降低程序性能,这个确实,但是有考虑过一个问题没有,我们系统大部分情况下都是正常运行,只会偶尔抛出异常,如果你写的程序一天到晚都在抛出异常,你是不是需要思考下,到底是你的问题还是程序的问题?同时,丢失掉这么一点点性能换来的是高可读、易维护、优雅的代码难道不值得吗?

最佳实践

既然 Exception 有诸多好处,那么我们应该怎么用好他呢?

我个人认为,程序中的错误可以分为三大类:

  • 系统错误。这类错误是程序运行环境的问题,一般我们都无法避免,对于这类错误,有些我们是可以处理的,比如请求网络异常,这个我们可以重试几次,而有些是我们无法处理的,比如内存耗尽 OOM 了、栈溢出等等,这种我们就只能停止运行,甚至退出整个程序。
  • 程序错误。这类错误一般都是我们程序的 bug,比如空指针,文件未创建,逻辑计算错误,对于这种错误,我们必须要记录下来,而且最好是触发监控系统告警。
  • 用户错误。比如用户输入非法参数,重复请求,一般这类的错误属于用户应用层错误,对于这类错误,我们只需要提示用户即可,没有必要记录日志,但是我们可以做一些必要的统计,比如某个用户频繁输入非法参数,不断进行错误请求,我们可以将这些用户纳入黑名单等,这样有利于我们改善系统和侦测是否有恶意的用户请求。

对于这三种错误,我们需要进行区分,不同的异常分类有不同的处理级别。

  • 系统错误:尽可能的预见异常,在能处理的地方需要进行处理,不能处理的往外抛
  • 程序错误:我们需要尽可能杜绝,记录每一个程序处理异常,进行必要的告警。
  • 用户错误:参数校验必须,严谨将错误参数带入系统。我们无法避免,只能适当统计和侦测。

同时,我不建议系统中定义太多自定义 Exception,有些小伙伴自定义 Exception 好像着魔了一样,在系统中定义一大把 Exception,如 UserNotFoundExceptionUserPasswordErrorException等等。目前我负责的系统中就只有如下几个异常:

  1. BusinessException:业务异常,继承 RuntimeException
  2. NotFountException:业务异常,继承 RuntimeException
  3. ParamValidateException:业务异常,继承 RuntimeException
  4. SystemException:系统异常,继承 Exception

1、2、3 不需要显示处理,4 一定需要处理。然后再配合两个枚举:

  1. BusinessErrorCodeEnum
  2. SystemErrorCodeEnum

同时 BusinessException,构造函数中有一个 isPrintLog 参数,用来判断是否需要打印 Error 日志,对于一些不需要我们关注的 Exception,统一传 false 即可。监控系统会每天将我们系统中所有的 Exception 通过邮件的方式发送给我们团队的每一个人,我每天都会去关注前一天系统中的 Error 日志,根据实际情况去掉一些不关注的 Exception。而且监控系统会实时将系统的 Error 、Exception 通过邮件和企业微信的方式告知相关的开发人员,这样我们就能及时发现系统这个的错误,及时响应,缩小影响范围。

下面是耗子叔的异常最佳实践,总结的非常好:

  • 统一分类的错误字典。无论你是使用错误码还是异常捕捉,都需要认真并统一地做好错误的分类。最好是在一个地方定义相关的错误。比如,HTTP 的 4XX 表示客户端有问题,5XX 则表示服务端有问题。也就是说,你要建立一个错误字典。
  • 同类错误的定义最好是可以扩展的。这一点非常重要,而对于这一点,通过面向对象的继承或是像 Go 语言那样的接口多态可以很好地做到。这样可以方便地重用已有的代码。
  • 定义错误的严重程度。比如,Fatal 表示重大错误,Error 表示资源或需求得不到满足,Warning 表示并不一定是个错误但还是需要引起注意,Info 表示不是错误只是一个信息,Debug 表示这是给内部开发人员用于调试程序的。
  • 错误日志的输出最好使用错误码,而不是错误信息。打印错误日志的时候,应该使用统一的格式。但最好不要用错误信息,而应使用相应的错误码,错误码不一定是数字,也可以是一个能从错误字典里找到的一个唯一的可以让人读懂的关键字。这样,会非常有利于日志分析软件进行自动化监控,而不是要从错误信息中做语义分析。比如:HTTP 的日志中就会有 HTTP 的返回码,如:404。但我更推荐使用像PageNotFound这样的标识,这样人和机器都很容易处理。
  • 忽略错误最好有日志。不然会给维护带来很大的麻烦。
  • 对于同一个地方不停的报错,最好不要都打到日志里。不然这样会导致其它日志被淹没了,也会导致日志文件太大。最好的实践是,打出一个错误以及出现的次数。不要用错误处理逻辑来处理业务逻辑。也就是说,不要使用异常捕捉这样的方式来处理业务逻辑,而是应该用条件判断。如果一个逻辑控制可以用 if - else 清楚地表达,那就不建议使用异常方式处理。异常捕捉是用来处理不期望发生的事情,而错误码则用来处理可能会发生的事。
  • 对于同类的错误处理,用一样的模式。比如,对于null对象的错误,要么都用返回 null,加上条件检查的模式,要么都用抛 NullPointerException 的方式处理。不要混用,这样有助于代码规范。
  • 尽可能在错误发生的地方处理错误。因为这样会让调用者变得更简单。向上尽可能地返回原始的错误。如果一定要把错误返回到更高层去处理,那么,应该返回原始的错误,而不是重新发明一个错误。
  • 处理错误时,总是要清理已分配的资源。这点非常关键,使用 RAII 技术,或是 try-catch-finally,或是 Go 的 defer 都可以容易地做到。
  • 不推荐在循环体里处理错误。这里说的是 try-catch,绝大多数的情况你不需要这样做。
  • 最好把整个循环体外放在 try 语句块内,而在外面做 catch。不要把大量的代码都放在一个 try 语句块内。一个 try 语句块内的语句应该是完成一个简单单一的事情。
  • 为你的错误定义提供清楚的文档以及每种错误的代码示例。如果你是做 RESTful API 方面的,使用 Swagger 会帮你很容易搞定这个事。
  • 对于异步的方式,推荐使用 Promise 模式处理错误。对于这一点,JavaScript 中有很好的实践。
  • 对于分布式的系统,推荐使用 APM 相关的软件。尤其是使用 Zipkin 这样的服务调用跟踪的分析来关联错误。

最后用一句总结今天的文章:异常应该出现在它应该出现的地方

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【死磕 Java 基础】 — 你以为异常就是 try…catch ?那你天真了 的相关文章

  • 基于Pytorch的模型推理

    训练部分说明 假设我们现在有两个文件 first file train py 用于训练模型 second file inference py 用于推理检测 在train py文件中我们使用了定义了一个类 里面声明了我的网络模型 例如 cla
  • java 备份sqlserver部分表_SQLServer备份指定表

    先定义表类型 CREATE TYPE t bak tables AS TABLE table name VARCHAR 200 然后再定义备份过程 CREATE PROC sp bak tables tbs t bak tables REA
  • 火线精英服务器怎样可以稳定,火线精英怎么操控好速度慢威力大的斩月

    关键词 火线精英怎么操控好速度慢威力大的斩月 火线精英斩月怎么操控 火线精英怎么操控好斩月 火线精英斩月怎么用 火线精英斩月是一把新出的刀 外格炫酷 威力惊人 美中不足的是他的攻速 这地方是硬伤啊 但是 作为新一代的神器 怎样才能操控好呢
  • Intellij Idea单元测试覆盖率插件JaCoCo的使用

    JaCoCo是Java代码单元测试覆盖率工具 可以用于IDE 也可以用于Maven等构建工具 IDE 主要介绍Intellij Idea中的使用 Eclipse请查看Jacoco Eclipse简单操作 Intellij Idea用法 调整
  • PXE自动化安装CentOS 8

    安装前准备 关闭防火墙和SELINUX DHCP服务器静态IP 切勿用桥接模式 最好是NET模式而且把自动分配DHCP对勾取消 查看防火墙和seelinux是否关闭 root centos8 firewall cmd state not r
  • Qt5 的类 QPoint,QPointF(相关的方法,不会的都在)

    QPoint QPoint官网链接 简介 QPoint 类使用整数精度来定义平面中的一个点 包含的成员 我觉得你可能看不懂的成员使用的方法 1 dotProduct const QPoint const QPoint int 参数是两个点的
  • 解决 Ubuntu 和其他发行版上的 WSL2 网络无法访问的问题

    翻译于Kontext 的 Resolution for WSL2 Network Unreachable Issue on Ubuntu and Other Distros 对于我的 Ubuntu 1 2 发行版 从 WSL20 升级到 W
  • Roblox学习笔记

    概述 Roblox笔记 可能会顺便写一下Lua相关的 1 Roblox中变化整个物体 1 通过组合为Model整体变换 问题概要 Unity中 改变父物体的Transform子物体会一起变换 Roblox中 在 属性 面板中有两个与位置相关
  • 智慧PG集成开发平台pgting-cli发布了

    介绍 两周前我们发布了智能页面搭建平台 智慧PG pgting 深受用户青睐 很多用户尝试了在线开发组件 为了方便用户定制开发组件和组件共享 智慧PG设计之初就考虑了组件定制开发问题 为此 我们设计和研发了智慧PG集成工作台pgting c
  • 软件测试金融项目,在测试的时候一定要避开的一些雷区

    软件测试金融项目需要格外谨慎和专注 因为这些项目通常涉及大量的交易 用户隐私和其他敏感信息 以下是一些软件测试金融项目时需要关注的方面 1 数据保护 在测试金融项目时 必须确保用户数据和投资信息得到保护 测试人员必须确保测试环境和测试数据安
  • 【C++】[boost]::enable_shared_from_this类实例

    原来误以为可以用作单例 生命周期与应用本身同寿那种 后来参考例子后发现是智能指针管理下的实例 1 需要继承自enable shared from this 比如说 class Y public Boost enable shared fro
  • 安装使用MMDeploy(Python版)

    安装使用MMDeploy Python版 一 安装 MMDeploy python mmdeploy main tools deploy py mmdeploy main configs mmdet detection detection

随机推荐

  • 蓝牙的知识总结(1)

    1 SoC System on Chip 称为系统级芯片 一个产品 是一个有专用目标的集成电路 其中包含完整系统并有嵌入软件的全部内容 同时它又是一种技术 用以实现从确定系统功能开始 到软 硬件划分 并完成设计的整个过程 从狭义角度讲 它是
  • 【算法】字符串算法题——回文(学习篇)

    验证回文串 给定一个字符串 验证它是否是回文串 只考虑字母和数字字符 可以忽略字母的大小写 说明 本题中 我们将空字符串定义为有效的回文串 示例 1 输入 A man a plan a canal Panama 输出 true 解释 ama
  • BlocklyGame (编译方法)分析

    先列出以下几个位置 本人用的是linux BlocklyGame源码位置 https github com google blockly games 获取源码 git clone https github com google blockl
  • Web 基础 之 Eclipse 中如何把新建的 jsp 文件默认的编码格式(ISO-8859-1)修改为 UTF-8,避免中文乱码

    Web 基础 之 Eclipse 中如何把新建的 jsp 文件默认的编码格式 ISO 8859 1 修改为 UTF 8 避免中文乱码 目录 Web 基础 之 Eclipse 中如何把新建的 jsp 文件默认的编码格式 ISO 8859 1
  • 网络安全中机器学习大合集

    目录 数据集 论文 书籍 演讲 教程 课程 杂项 数据集 安全相关数据样本集 DARPA 入侵检测数据集 Stratosphere IPS 数据集 开放数据集 NSA 的数据捕获 ADFA 入侵检测数据集 NSL KDD 数据集 恶意 UR
  • arm ldr/ld/数据加载系列指令和adr指令

    ldrb指令 1 语法 armv7手册语法 LDRB
  • Android中收听特定应用安装成功的广播

    一 manifest的配置
  • C语言当中的分段函数求解

    对于学习C语言的小白来说 经常会遇到求解分段函数的问题 下面是小编写的一段求解分段函数的代码 希望对于初学的你们有所帮助 代码求解的分段函数为 y f x 4x 3 x lt 15 y f x 2 5x 10 5 x gt 15 inclu
  • 网络编程--TCP/IP协议(一)

    目录 前言 一丶网络基础 lt 1 gt 认识IP地址 1 gt 具体格式 2 gt 组成 3 gt 分类 4 gt 子网掩码 lt 2 gt 认识MAC地址 二丶网络设备及相关技术 lt 1 gt 物理层 集线器 lt 2 gt 数据链路
  • C++:实现计算贷款支付额问题

    计算月支付额的公式如下 月支付额 贷款总额 月利率 1 1 1 月利率 年数 12 include
  • win10使用腾讯会议软件没声音怎么解决

    购买了一个USB接口的带微麦网络摄像头 在win10电脑上打开腾讯会议软件进入会议后讲话始终没有声音输出 经尝试 排除麦克风和声卡音箱硬件和驱动的原因之后 Win10操作系统上腾讯会议麦克风能接收到输入但没有声音播出 一般会是麦克风设置和w
  • 什么是数据产品,如何设计一款好用的数据产品

    何为数据产品 从广义上讲 一切以数据作为驱动或者核心的都叫数据产品 例如数据报表平台 DMP 搜索与精准化产品 风控产品等等 从狭义上讲 就是公司的内部数据平台 今天和大家讨论的 主要是关于公司的内部数据平台的搭建 公司的内部数据平台 主要
  • 【满分】【华为OD机试真题2023B卷 JAVA&JS】计算最大乘积

    华为OD2023 B卷 机试题库全覆盖 刷题指南点这里 计算最大乘积 知识点字符串位运算 时间限制 1s 空间限制 32MB 限定语言 不限 题目描述 给定一个元素类型为小写字符串的数组 请计算两个没有相同字符的元素 长度乘积的最大值 如果
  • GAN网络系列博客(一):用于人脸生成的StyleGAN结构

    目录 简介 基于风格的生成器 生成器的性质 总结 Reference 在未来的一段时间 我会开一个小专题 来介绍下GAN网络的一些经典论文 希望对那些想要入坑的同学提供一点点帮助 考虑到StyleGAN系列论文在相关领域的影响力 我们首先来
  • QT 多线程实现方式

    前因 当调用QApplication exec 时 就启动了QT的事件循环 在开始的时候QT会发出一些事件命令来显示和绘制窗口部件 在这之后 事件循环就开始运行 它不断检查是否有事件发生并且把这些事件发生给应用程序的QObject 当处理一
  • 微信小程序使用van-tabs组件,ios真机z-index层级错乱问题【已解决,ios自定义组件层级不穿透】

    一 这是模拟器上的效果 二 这是苹果11真机上的效果 安卓真机正常 三 先来理一下代码的层级现状 A 为van tabs B 是自定义组件 为数据列表 C为单个数据 D 也是自定义组件 图中省略 为单个数据详情弹窗 且D是B的子组件 在z
  • 题目0158-快递业务站

    快递业务站 题目描述 快递业务范围有 N 个站点 A 站点与 B 站点可以中转快递 则认为 A B 站可达 如果 A B 可达 B C 可达 则 A C 可达 现在给 N 个站点编号 0 1 n 1 用 s i j 表示 i j 是否可达
  • 信任的机制——区块链

    区块链是一个从顶向下实现的一项技术 是可以设计 可以编程的 区块链是一个信任的机器 是在完全不信任的节点之间建立信任机制的技术 是利用互联网传递价值的一种价值网络 这是一个把时间当朋友的技术 区块链在应用的过程中通过自身的设计 解决的问题
  • SAX解析xml

    第一步 创建xml
  • 【死磕 Java 基础】 — 你以为异常就是 try…catch ?那你天真了

    大家好 我是大明哥 个人网站 https www cmsblogs com 前言 我敢说对于很多小伙伴来说 他们以为在 Java 中异常就是 try catch 稍微有点儿意识的还会用下 throw new Exception 真的有这么简