异常处理,究竟是处理什么

“系统中每行代码,都应该是有意义的,如果一段代码可有可无,那它就不应该存在。”

01—内容简述

异常处理是软件开发的必备技能,但异常处理,究竟是处理什么?,很多小伙伴并没有一个清晰的认识,大部分人的认识停留在给代码加上try/catch就算是异常处理了,至于异常捕获之后该做什么,并不清楚。本文阐述一下自己对异常处理的思考,希望帮助大家对异常处理有一个清晰的认识,在面对异常处理时,能够有准确的决策。.

02—区分异常和错误

首先要区分哪些是异常,哪些是错误。
可以预测,可以通过代码逻辑避免发生的异常,我称之为“错误”。
无法预测,无法通过代码逻辑避免的异常,我称之为“异常”。

相信到这里,很多小伙伴心里已经有答案了,比如,平时遇到最多的——空引用异常,应该算是错误,这是可以通过代码逻辑避免的。而真正的异常是无法预料的,比如:网络中断、请求超时、堆栈溢出、第三方服务异常…等。

我们区分了异常和错误,那么对于属于错误的部分,最佳的处理方案是通过代码逻辑,尽量控制,避免错误的发生,而对于属于异常的部分,或者遗漏的,没有控制的错误,该如何处理?接下来便是本文的重点。

03—异常处理要做什么

异常处理概括起来要做三件事:

1.记录异常日志

记录异常日志是为了后续排查,定位,解决异常问题,需要记录的信息包括:

异常发生时间,异常代码位置,以及异常时上下文信息(参数),当前操作人等;

如果是Web请求,还要记录请求的IP地址,URL,客户端设备信息;

如果是SOA或微服务架构,还要记录调用链路标识TraceID。

记录异常信息的原则是:帮助开发人员还原异常现场,快速定位异常原因,以便尽快修复。

2.确保非托管资源的关闭和释放

包括:数据库链接,IO流,等非托管资源的处理,在发生异常的情况下,如何正确关闭链接,释放资源,是需要重点考虑的。

推荐使用 using 语句声明非托管资源,而不是用try/catch,可以查看我之前的文章 C# using()的本质

3.处理异常后续业务逻辑

主要是业务处理逻辑中事先规划的异常发生后的处理逻辑,比如:

某个服务接口请求超时后进行重试;

某个功能操作失败后,切换到备选方案(降级)或触发补偿事件;

在多线程中,当某个子线程异常之后,取消其他相关子线程操作;

自定义异常的处理。

上述情况要根据实际的业务场景决定,如果系统设计时并没有规划,也就不需求去处理这部分功能。

04—处理方案

明确了异常处理的三件事,接着看具体方案。
第一件事:记录异常日志
毫无疑问,应该由全局异常处理组件记录异常日志,如果框架有全局异常处理功能,就使用框架提供的异常处理功能,如果框架没有,那就自己开发一个。在Asp.Net中,可以使用ExceptionFilter或自定义中间件实现全局异常处理
第二件事:确保非托管资源关闭和释放
由上文所说,使用using就好。
第三件事:处理异常后续业务逻辑

视实际业务场景而定,可以在代码中加try/catch,也可以开发通用的异常处理组件,让业务逻辑更简洁清晰。

05—总结

异常处理规范建议:
  1. 通过代码规范,尽量减少系统中可控的异常(错误)发生。

  2. 通过全局异常组件,记录异常日志,处理通用异常。

  3. 仅在业务逻辑明确需求的情况下,使用try/catch处理异常。

06—成长轨迹

有小伙伴,习惯在每个方法中都加try/catch,却说不清为何这么做,大概的理由是:程序总难免会报错,所以要进行try/catch。根本原因在于:对代码运行机制不够理解,对自己的代码没有信心,以及对异常处理认识的模糊。这些try/catch是不应该出现的,会对代码简洁性,可读性造成污染,与优雅代码背道而驰。我自己也是从这个阶段过来的,也希望小伙伴们不要一直停留在这个阶段。
我第一次对异常处理有深刻认知是得到了一位微软首席架构师的指点,这位架构师是沈征,一位大师级的技术专家,既能讲理论,画架构图,又能写出优雅的框架代码,是我多年职业生涯中遇到的为数不多的大师,他的几句话足以让人在迷雾之中看清前方的路,看见远方的山,看到头顶的太阳和蓝天,再次向大师致敬。