hi ,大家好,我是三天打鱼,两天晒网的小六六,欢迎大家关注我的公众号:"六脉神剑的程序人生",一起学习,一起进步

前言

今天六脉神剑第二章又来问我问题了,还特么给我发了一堆表情,我特么直呼好家伙

aebb0a06-9124-49d5-859b-e4ecc92130cf.jpg

image.png

当然我是不可能这么回他的,毕竟是我为数不多的粉丝,所以来关注下小六六吧,我的每位粉丝都能得到我精准的呵护,所以呀,就回了它下面的话

2f3713ef-a42a-414a-8d25-038a82ca61d3.jpg

大家发现没有,这回六哥硬气了,没有秒回这位粉丝了,哈哈 好家伙!不再是舔狗了,言归正传:下面就给大家分享分享我们之前异常到底要怎么处理,才是真正的最佳实践!

一个案例

是这样的,这个案例是小六六自身经历的一个例子哈,我想大家都用过微服务吧!我先说说背景哈,我有这样的一个场景,就是我支付完成之后,我是不是要去给用户发货,但是给用户发挥之前,我需要调用一下风控的服务,如果这个用户风控判断有风险的话,我就会拦截这个发货行为,因为这个系统是做海外系统的,那我再调用风控系统之前要做这样一个事情,就是把海外的本地币种转化为USD的统一的汇率,这就设计到了汇率的服务了,然后呢有一天假设我做了一些更改,然后需要把我支付这个服务,和我调用汇率接口的服务一起升级,但是因为我不小心,导致忘记发汇率接口,然后就会出现一个问题,因为我汇率服务本身,里面是处理了异常的,就算我没有升级这个服务,你一样也可以调通这个服务,但是我会把我自己的异常转换成code,然后支付调用汇率这个服务,根据code的值成功或者失败去做逻辑,但是呢因为我支付服务,如果调用成功的话,我就把转换后的金额传给风控,如果转换返回失败,我就给一个默认值0,但是这样一写逻辑就碰到一个问题,风控那边会根据金额来做策略,它有一个策略就是拦截金额为0的订单,不发货,所以那一次升级,就是因为这个逻辑导致了有6k多单的发货没有被发货,导致了这个事故,像上面这个问题,其实我们觉得我们不应该说帮人家去做决策,如果失败的话,我们不应该说给一个默认值,而是抛异常出去才对,这才是正确的做法,就是很多时候,我们自己并不知道是给一个业务code的错误,还是抛一个Exception,像很多其他的不那么严谨的业务,可能并不说考虑的那么清楚,但是我们支付就必须一点点都得考虑的很严谨了,像这个事故,我们还有很多不足的地方,延迟发货的告警没有告,调用汇率接口失败了,也没有告警,而是把原生的错误转换了等等。所以小六六这边才觉得,很多的时候,我们自己确实是不知道如何的处理一些业务的异常,应该怎么样给其他服务返回,才能让调用你的服务的人,觉得你这个服务的设计上好的,等等,这就是我想跟大家聊的这篇文章。

不过我们还是先来了解下Java的异常体系吧!

image.png

什么是异常

异常是程序中的一些错误,但并不是所有的错误都是异常,并且错误有时候是可以避免的。

比如说,你的代码少了一个分号,那么运行出来结果是提示是错误java.lang.Error;如果你用System.out.println(11/0),那么你是因为你用0做了除数,会抛出java.lang.ArithmeticException的异常。

异常发生的原因有很多,通常包含以下几大类:

  • 用户输入了非法数据。

  • 要打开的文件不存在。

  • 网络通信时连接中断,或者JVM内存溢出。

要理解Java异常处理是如何工作的,你需要掌握以下三种类型的异常:

  • 检查性异常: 最具代表的检查性异常是用户错误或问题引起的异常,这是程序员无法预见的。例如要打开一个不存在文件时,一个异常就发生了,这些异常在编译时不能被简单地忽略。

  • 运行时异常: 运行时异常是可能被程序员避免的异常。与检查性异常相反,运行时异常可以在编译时被忽略。

  • 错误: 错误不是异常,而是脱离程序员控制的问题。错误在代码中通常被忽略。例如,当栈溢出时,一个错误就发生了,它们在编译也检查不到的。

Java异常的体系结构

Java把异常当作对象来处理,并定义一个基类java.lang.Throwable作为所有异常的超类。

在Java API中已经定义了许多异常类,这些异常类分为两大类,错误Error和异常Exception

Java异常层次结构图如下图所示:

image.png

在Java中,所有异常类的父类是Throwable类,Error类是error类型异常的父类,Exception类是exception类型异常的父类,RuntimeException类是所有运行时异常的父类,RuntimeException以外的并且继承Exception的类是非运行时异常。

  • ErrorError类对象由 Java 虚拟机生成并抛出,大多数错误与代码编写者所执行的操作无关。例如,Java虚拟机运行错误(Virtual MachineError),当JVM不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止;还有发生在虚拟机试图执行应用时,如类定义错误(NoClassDefFoundError)、链接错误(LinkageError)。这些错误是不可查的,因为它们在应用程序的控制和处理能力之 外,而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。在Java中,错误通常是使用Error的子类描述。

  • Exception:在Exception分支中有一个重要的子类RuntimeException(运行时异常),该类型的异常自动为你所编写的程序定义ArrayIndexOutOfBoundsException(数组下标越界)、NullPointerException(空指针异常)、ArithmeticException(算术异常)、MissingResourceException(丢失资源)、ClassNotFoundException(找不到类)等异常,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生;而RuntimeException之外的异常我们统称为非运行时异常,类型上属于Exception类及其子类,从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOExceptionSQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。

Java 异常的处理机制

Java的异常处理本质上是抛出异常捕获异常

  • 抛出异常:要理解抛出异常,首先要明白什么是异常情形(exception condition),它是指阻止当前方法或作用域继续执行的问题。其次把异常情形和普通问题相区分,普通问题是指在当前环境下能得到足够的信息,总能处理这个错误。对于异常情形,已经无法继续下去了,因为在当前环境下无法获得必要的信息来解决问题,你所能做的就是从当前环境中跳出,并把问题提交给上一级环境,这就是抛出异常时所发生的事情。抛出异常后,会有几件事随之发生。首先,是像创建普通的java对象一样将使用new在堆上创建一个异常对象;然后,当前的执行路径(已经无法继续下去了)被终止,并且从当前环境中弹出对异常对象的引用。此时,异常处理机制接管程序,并开始寻找一个恰当的地方继续执行程序,这个恰当的地方就是异常处理程序或者异常处理器,它的任务是将程序从错误状态中恢复,以使程序要么换一种方式运行,要么继续运行下去。

举个简单的例子,假使我们创建了一个学生对象Student的一个引用stu,在调用的时候可能还没有初始化。所以在使用这个对象引用调用其他方法之前,要先对它进行检查,可以创建一个代表错误信息的对象,并且将它从当前环境中抛出,这样就把错误信息传播到更大的环境中。

if(stu == null){throw new NullPointerException();
}
  • 捕获异常:在方法抛出异常之后,运行时系统将转为寻找合适的异常处理器(exception handler)。潜在的异常处理器是异常发生时依次存留在调用栈中的方法的集合。当异常处理器所能处理的异常类型与方法抛出的异常类型相符时,即为合适的异常处理器。运行时系统从发生异常的方法开始,依次回查调用栈中的方法,直至找到含有合适异常处理器的方法并执行。当运行时系统遍历调用栈而未找到合适的异常处理器,则运行时系统终止。同时,意味着Java程序的终止。

Java异常处理涉及到五个关键字,分别是:trycatchfinallythrowthrows。下面将骤一介绍,通过认识这五个关键字,掌握基本异常处理知识。

• try        -- 用于监听。将要被监听的代码(可能抛出异常的代码)放在try语句块之内,当try语句块内发生异常时,异常就被抛出。
  • catch   -- 用于捕获异常。catch用来捕获try语句块中发生的异常。
  • finally  -- finally语句块总是会被执行。它主要用于回收在try块里打开的物力资源(如数据库连接、网络连接和磁盘文件)。只有finally块,执行完成之后,才会回来执行try或者catch块中的return或者throw语句,如果finally中使用了return或者throw等终止方法的语句,则就不会跳回执行,直接停止。
  • throw   -- 用于抛出异常。
  • throws -- 用在方法签名中,用于声明该方法可能抛出的异常。

项目中到底要怎么去处理异常呢

小六六这边分2种情况来说说,一种就是我们一般的后台管理系统,一种是类似于支付系统的C端项目,再我的感觉中,它们对异常处理的细粒度是不一样的。

一般的后台管理系统的方式

一个异常枚举类,所有异常码定义在这里:

public enum BizExceptionEnum {APPLICATION_ERROR(1000, "网络繁忙,请稍后再试"),INVALID_USER(1001, "用户名或密码错误"),INVALID_REQ_PARAM(1002, "参数错误"),EXAM_NOT_FOUND(1003, "未查到考试信息"),;BizExceptionEnum(Integer errorCode, String errorMsg) {this.errorCode = errorCode;this.errorMsg = errorMsg;}private final Integer errorCode;private final String errorMsg;// get......
}

一个业务异常类:

public class BizException extends RuntimeException {private final BizExceptionEnum bizExceptionEnum;public BizException(BizExceptionEnum bizExceptionEnum) {super(bizExceptionEnum.getErrorMsg());this.bizExceptionEnum = bizExceptionEnum;}public BizExceptionEnum getBizExceptionEnum() {return bizExceptionEnum;}
}

一个全局的异常处理类:

@RestControllerAdvice
public class GlobalHandler {private final Logger logger = LoggerFactory.getLogger(GlobalHandler.class);@ExceptionHandler(MethodArgumentNotValidException.class)public Result exceptionHandler(MethodArgumentNotValidException e) {Result result = new Result(BizExceptionEnum.INVALID_REQ_PARAM.getErrorCode(),BizExceptionEnum.INVALID_REQ_PARAM.getErrorMsg());logger.error("req params error", e);return result;}@ExceptionHandler(BizException.class)public Result exceptionHandler(BizException e) {BizExceptionEnum exceptionEnum = e.getBizExceptionEnum();Result result = new Result(exceptionEnum.getErrorCode(), exceptionEnum.getErrorMsg());logger.error("business error", e);return result;}@ExceptionHandler(value = Exception.class)public Result exceptionHandler(Exception e) {Result result = new Result(BizExceptionEnum.APPLICATION_ERROR.getErrorCode(),BizExceptionEnum.APPLICATION_ERROR.getErrorMsg());logger.error("application error", e);return result;}}

其中Result类的定义:

public class Result<T> {private Boolean success;private Integer errorCode;private String errorMsg;private T data;public Result(T data) {this(true, null, null, data);}public Result(Integer errorCode, String errorMsg) {this(false, errorCode, errorMsg, null);}public Result(Boolean success, Integer errorCode, String errorMsg, T data) {this.success = success;this.errorCode = errorCode;this.errorMsg = errorMsg;this.data = data;}// get set......
}

示例Controller类:

@RestController
@RequestMapping("/json/exam")
public class ExamController {@Autowiredprivate IExamService examService;@PostMapping("/getExamList")public Result<List<GetExamListResVo>> getExamList(@Validated @RequestBody GetExamListReqVo reqVo,@AuthenticationPrincipal UserDetails userDetails)throws IOException {List<GetExamListResVo> resVos = examService.getExamList(reqVo, userDetails);Result<List<GetExamListResVo>> result = new Result(resVos);return result;}
}

其中Result类的定义:

public class Result<T> {private Boolean success;private Integer errorCode;private String errorMsg;private T data;public Result(T data) {this(true, null, null, data);}public Result(Integer errorCode, String errorMsg) {this(false, errorCode, errorMsg, null);}public Result(Boolean success, Integer errorCode, String errorMsg, T data) {this.success = success;this.errorCode = errorCode;this.errorMsg = errorMsg;this.data = data;}// get set......
}

示例Controller类:

@RestController
@RequestMapping("/json/exam")
public class ExamController {@Autowiredprivate IExamService examService;@PostMapping("/getExamList")public Result<List<GetExamListResVo>> getExamList(@Validated @RequestBody GetExamListReqVo reqVo,@AuthenticationPrincipal UserDetails userDetails)throws IOException {List<GetExamListResVo> resVos = examService.getExamList(reqVo, userDetails);Result<List<GetExamListResVo>> result = new Result(resVos);return result;}
}

示例Service类:

@Service
public class IExamServiceImpl implements IExamService {@Autowireprivate ManualMicrowebsiteMapper microwebsiteMapper;@Overridepublic List<GetExamListResVo> getExamList(GetExamListReqVo reqVo, UserDetails userDetails) throws IOException {List<MicrowebsiteExam> examEntities = microwebsiteMapper.select(reqVo.getExamType(), userDetails.getUsername());// 按照业务的定义要求,此处考试列表必须不为空,一旦为空,则说明后台配置有误或其它未知原因,这种情况视为一种业务异常if (examEntities.isEmpty()) {// 未查到考试信息,抛出相应的业务异常throw new BizException(BizExceptionEnum.EXAM_NOT_FOUND);}// 此处代码还有其它各类异常抛出......List<GetExamListResVo> resVos = examEntities.stream().map(examEntity -> {GetExamListResVo resVo = new GetExamListResVo();BeanUtils.copyProperties(examEntity, resVo);return resVo;}).collect(toList());return resVos;}
}

C端项目的例子

  • 其实,C端项目大体和上面说一致的,但是我们一般都是微服务进行开发,那么我们应该一开始就给每个服务的业务异常码返回一个范围,这样就能从请求的源头就能知道错误的点在哪个系统,这是第一个点吧

  • 第二个,其实对于每个微服务,和上面的异常处理上一样的,但是我想说的是对于上面处理的Service,我们应该对里面的业务异常更加细腻的去处理,因为我们只是抛出了一些我们能预判到的一些业务异常,但是一些比如JSON转换异常等等异常,我们要到最外层去处理,但是最外层也只是把这个异常转换成大异常了,这样就是说对于C端项目来说,这样异常的力度,应该不是不够的,我们应该再细分一下,就是尽可能的把一些可能的异常转换成我们业务异常,这样的话,我们代码的健壮性就会好很多,而且很多的异常展示给用户的文案也是可以统一转换的。我们来看下面一个Service解绑的业务的例子吧!

// 1根据传入条件判断该派安盈用户存在,状态是normal
ThirdAcct thirdAcct = null;
try {thirdAcct = thirdAcctRelationService.getNormalThirdAcct(chId, userType, thirdUserId, payMethod, chAccountId);
} catch (DataAccessException e) {throw new UIDataAccessException(e.getCode(), e.getMessage(), "fail.thirdAcct.queryfail",e);
}//1.1不存在说明已经删除过了
if (ObjectUtil.isNull(thirdAcct)) {return Boolean.TRUE;
}//1.2存在,证明等待删除
// 2.获取账号的渠道用于调取派安盈接口
ChAccount chAccount = null;
try {chAccount = chAccountRelationService.getChAccount(appId, chId, payMethod, chAccountId);
} catch (DataAccessException e) {//di装bithrow new UIDataAccessException(e.getCode(), e.getMessage(), "fail.chAccount.miss",e);
}
if (ObjectUtil.isNull(chAccount)) {throw new UIDataAccessException("fail.chAccount.miss", ",获取渠道账号信息失败", "fail.chAccount.miss");
}ChannelRelationService channelService = this.getChannelRelationService(chId);
if (channelService == null) {//没有相关渠道throw new UIDataAccessException("fail.channel.unfound", "找不到相关渠道的service,chId:"+chId, "fail.channel.unfo```
//解绑
try {channelService.unbind(chAccount,thirdAcct);
} catch (DataAccessException e) {throw new UIDataAccessException(e.getCode(), e.getMessage(), "fail.thirdChannel.bindFail",e);
}//4.解绑,注意这些多个联合组件的,要根据条件来解绑的,
try {thirdAcctRelationService.unbindThirdAcct(thirdAcct);
} catch (DataAccessException e) {throw new UIDataAccessException(e.getCode(), e.getMessage(), "fail.dbthirdacct.bindFail",e);}

我们可以看到,一个业务可能拆分多个子业务,那么每个子业务都有可能抛不同的异常,你要再不同的子业务中把它们的业务转换成ui的异常,只要你的系统够完善,那么意外的异常就会非常少,这样下去你的系统会越来越稳定的。

结束

好了,今天小六六的分享就到这了,可能很多小伙伴看了会觉得没啥东西,那是因为你没有体验过一个C端产品的严谨性,如果仅仅是一个后台管理,确实是不必要这样的,但是对于面向用户的产品,我觉得异常处理的好坏,决定了你这个产品的系统质量!好了,我是小六六,三天打鱼,两天晒网!

写了这么久的业务连异常都不知道怎么处理吗相关推荐

  1. 写了这么久前端,你知道浏览器原理吗?

    最近发现了一篇不错的文章,分享给大家,对于写了很久前端代码但不了解浏览器原理的朋友应该有很大帮助. 这是一篇全面介绍 Webkit 和 Gecko 内部操作的入门文章,是以色列开发人员塔利·加希尔大量 ...

  2. 写代码犹如写文章: “大师级程序员把系统当故事来讲,而不是当做程序来写” | 如何架构设计复杂业务系统? 如何写复杂业务代码?

    写代码犹如写文章: "大师级程序员把系统当故事来讲,而不是当做程序来写" | 如何架构设计复杂业务系统? 如何写复杂业务代码? Kotlin 开发者社区 "大师级程序员把 ...

  3. 写if-else不外乎两种场景:异常逻辑处理和不同状态处理。

    写if-else不外乎两种场景:异常逻辑处理和不同状态处理. 参考文章: (1)写if-else不外乎两种场景:异常逻辑处理和不同状态处理. (2)https://www.cnblogs.com/hu ...

  4. 用Java写PTA 7-11 设计一个能处理异常的Loan类

    用Java写PTA 7-11 设计一个能处理异常的Loan类 定义一个贷款类Loan,其中有属性: annualInterestRate:double,表示贷款的年利率(默认值:2.5) number ...

  5. python桌面开发吐血_想用java写个桌面小demo,就布局都差点写吐血了,学艺不精...

    demo简略需求 项目背景 很多文件重复存放,除了管理混乱,还会对患有强迫症用户的身心造成10000点的伤害...其实就是360云盘当时上传了有上传,造成很多重复的图片+视频,前阵子360个人云盘&q ...

  6. 【写博客常用】x86,x64,arm都是什么

    [写博客常用]x86,x64,arm都是什么 指令集架构 指令集 参考文章 指令集架构 指令集架构主要分两大类. 复杂指令集运算(Complex Instruction Set Computing,C ...

  7. 今天我给大家分享几款珍藏已久的指标公式,都是简单好用的指标

    今天我给大家分享几款珍藏已久的指标公式,都是简单好用的指标文华财经--指标合集 1[决战]主图指标用过的都说好,可以比较明显的抓住涨跌 2[捉牛]主图指标游资大佬李先生呕心沥血之作,信号稳定不漂移,牛 ...

  8. 那些坚持写博客的程序员,后来都怎么样了?

    作者 | web小二 来源 |  web前端开发(web_qdkf) 如果你不是那最好的5%的程序员,编程也不可能成为你的决定性技能的话,那有什么办法可以让自己成为顶尖人才? 对于今天这个有点浮躁的社 ...

  9. 如何写出一手好的业务代码?

    以下文章来源方志朋的博客,回复"666"获面试宝典 这里举一个非常简单的例子,以案例的业务实现来分析如何写好业务代码. ★ 本案例只是简单的模拟,可能与真实的情况有出入,这里只是为 ...

  10. 写java线程导致电脑内存不足_如何写出让java虚拟机发生内存溢出异常OutOfMemoryError的代码...

    程序小白在写代码的过程中,经常会不经意间写出发生内存溢出异常的代码.很多时候这类异常如何产生的都傻傻弄不清楚,如果能故意写出让jvm发生内存溢出的代码,有时候看来也并非一件容易的事.最近通过学习< ...

最新文章

  1. (C++)1040 有几个PAT
  2. 获取窗口句柄模拟鼠标点击
  3. ssh登录一段时间后断开的解决方案
  4. CF 529B Group Photo 2 (online mirror version)
  5. java api 英文_JAVA中英文API(在线版)
  6. decimal函数python_decimal数据类型
  7. vs2015+opencv3.4安装及问题整理
  8. (转)IIS tomcat共用80端口解决一个IP多个域名:使用Nginx反向代理方式使两者兼容...
  9. 如何在页面上输出html标签:符号实体
  10. python匿名函数lambda_python的匿名函数lambda解释及用法
  11. java算法实验标尺问题_在codeigniter项目中使用标尺库
  12. native vlan(本征vlan)----vlan 1 与一般vlan的区别详细解答、tag字段简介、二层异vlan通信、双vlan tag跳跃攻击(附图,建议电脑观看)
  13. php验证规则表单,PHP Yii框架之表单验证规则大全
  14. 乐视android系统耗电量大,乐视手机突然充电慢耗电快,怎么解决?
  15. 附上一张公司项目解决方案的工程图
  16. 360网络修复大师_360补丁大师免安装下载-360补丁大师下载 v8.0 官方最新版
  17. Maven基础篇之Maven实战入门-最新IDEA版maven
  18. C++开源项目:十行代码15个BUG,你入坑了吗?
  19. Discuz的sc 和tc版本有什么区别
  20. Antd DatePicker之旬查询(本月上旬、本月中旬、本月下旬)

热门文章

  1. 从小就对生意耳濡目染的小伙,长大后创业资产过亿
  2. Datawhale:愿竞赛圈少一些人身攻击和热点炒作
  3. 我说MySQL联合索引遵循最左前缀匹配原则,面试官让我回去等通知
  4. 2021 ICPC Jinan C Optimal Strategy
  5. 蓝的成长记——追逐DBA(11):回家后的安逸,晕晕乎乎醒了过来
  6. npm init vite@latest 报错
  7. C语言笔记 隐藏光标函数 带注释(详细)
  8. easyui Datagrid的deleteRow多行移除问题
  9. 【IDEA】idea取消英语拼音提示绿色波浪线
  10. 软件测试人员必备的60个测试工具,果断收藏了!