本文章谈论的是《每个程序员应该知道的97件事》里的第21件事
区分业务异常和技术异常
(Distinguish Business Exceptions from Technical)
老规矩,先分享书里这个章节的大意,再写“命题作文”
错误处理是业务逻辑不可或缺的一部分。业务执行出现错误是一件很正常的情况,无论是因为使用不当,或者外部依赖出错,或者触发了未知的代码缺陷。 使用异常处理机制只是实现业务错误处理的一种常见实现方式,但不是唯一的方式。当遇到通过接口返回结果表示业务错误的代码实现,我们不需要惊讶或者气愤为什么不用异常去实现。虽说前者在大多场景更加合理,尤其当实现框架类代码时,但是后者并不是罕见的情况,而且选择异常处理机制我们也不一定正确处理了业务错误。 当然,使用异常去处理业务错误,依旧是一名好的程序员该有的追求。
标准异常(standard exception)指的是编程语言框架定义的异常。 标准异常的层次结构和具体类型都是编程语言专家经过多年的理论学习和实践经验提炼出来的。 当你学习异常处理时,你应该先要理解这些标准异常,因为它归纳了大量业务领域无关的典型异常类型。
当处理代码中可能出现的异常,我们不能为了虚伪的健壮性而盲目掩盖所有的异常。当代码的执行无法从异常中恢复,我们应该如实去向调用者汇报异常;这时候我们能做的只是确保抛出的异常能够让调用者准确理解错误,并执行相应的业务错误处理逻辑。 这解释了为什么最佳实践建议捕获异常的代码需要指定具体的异常类型,而不能捕获通用异常(catch general exception)。错误不能及早发现,它可能会带来更严重的后果,或者让我们更难调试问题的原因。比如遇到应用启动初始化的异常,我们往往需要阻止应用成功启动,而不是任由应用正常启动,否则错误的初始化下的应用可能会做出一些我们难以理解的行为。
我认为没什么特殊的技巧,只要你写代码的时候能回答以下问题就可以了
程序在运行时( runtime )报错主要有两种原因,阻止我们使用应用程序的技术问题和和阻止我们误用应用程序的业务逻辑。如果都使用异常来表示这两种情况,我们需要小心区分,不能使用同一种异常层次来表示技术异常和业务异常。技术异常往往交给应用程序的框架层去处理,而业务领域的异常应该交给客户端代码处理。 https://97-things-every-x-should-know.gitbooks.io/97-things-every-programmer-should-know/content/en/thing_21/
原文推荐:⭐⭐⭐⭐
个人简评:如何写好异常,这是一个老生常谈却一直难有答案的问题。虽然原文并没有系统地描述如何解决这个问题,但作者结合了一些实践的例子分享了自己的观点。读读多少会有参考价值。
理解业务错误才能做好异常处理
相比区分业务异常(business exception)和技术异常(technical exception), 我认为实现异常处理的困扰更多源于没深入理解业务的错误处理。
错误处理是业务问题,异常处理是技术手段
异常处理是编程语言的一个功能。学习它并不困难。然而,在实际工作中我们往往不清楚该如何做好异常处理。
“对于这个错误,我该返回哪个异常?”
“什么时候我该返回异常,什么时候我该返回错误编码或一个包含错误状态和细节信息的数据结构?”
一般来说,这多数源于我们过于重视异常处理的实现细节,却忽视了对业务错误的理解。将异常区分成业务异常和技术异常便是一种容易让人混淆业务错误处理和异常处理。前者是业务逻辑的一部分,后者是一个实现前者的其中一种常见的实现方式。严格来说,所有的异常都应该是为业务服务的;不为业务服务的异常,往往是需要修复的代码缺陷。 用之前文章里的一幅图形容,异常处理是编程语言领域知识,而错误处理是业务领域知识。也可以简单类比领域模型驱动设计和面向对象设计。前者关注的业务领域模型,后者关注的是如何使用对象和对象间的关系去表达前者。 在实际项目工程中,领域知识往往比技术知识更加重要。这多少解释了为什么学习面向对象设计和编程不难,但真正根据产品或功能的业务需求做好面向对象设计和编程往往需要长时间在业务领域的积累。因此,要做好异常处理,我们首先需要理解业务领域里的错误处理。如此,我们写的异常处理能够及早地准确地表达业务错误。说程序员的门槛低,往往说的是学习一门编程语言或通用技术的难度不大。但是,这不表示进入某个业务领域的门槛低。而程序员在实际工作中积累的价值更多源于业务领域,而非脱离业务的特定技术知识。
异常处理只是实现错误处理业务逻辑的一种方式
错误处理是业务逻辑不可或缺的一部分。业务执行出现错误是一件很正常的情况,无论是因为使用不当,或者外部依赖出错,或者触发了未知的代码缺陷。 使用异常处理机制只是实现业务错误处理的一种常见实现方式,但不是唯一的方式。当遇到通过接口返回结果表示业务错误的代码实现,我们不需要惊讶或者气愤为什么不用异常去实现。虽说前者在大多场景更加合理,尤其当实现框架类代码时,但是后者并不是罕见的情况,而且选择异常处理机制我们也不一定正确处理了业务错误。 当然,使用异常去处理业务错误,依旧是一名好的程序员该有的追求。
学习异常应该从编程语言的标准异常开始
标准异常(standard exception)指的是编程语言框架定义的异常。 标准异常的层次结构和具体类型都是编程语言专家经过多年的理论学习和实践经验提炼出来的。 当你学习异常处理时,你应该先要理解这些标准异常,因为它归纳了大量业务领域无关的典型异常类型。
《97件事》这一章节将标准异常成为技术异常,因为大多标准异常揭示的是代码的技术缺陷问题。但是,异常始终需要为具体业务服务,包括标准异常,因此我并不觉得这是一个好的观点。
当处理异常的时候,大多数情况我们会遇到标准异常,因此准确理解每个标准异常的定义和常用业务场景是很重要的。比如,当你看到抛标准异常的日志,你往往看到异常名称就可能猜到原因是什么了。大多标准异常揭露了代码缺陷,这些多数可以修复,无论是直接修复,还是通过Tester-Doer模式或Try-Parse模式避免了通过异常处理的形式处理业务错误。还有一些标准异常,当前代码无法处理,则需要向上汇报异常。Tester-Doer: if xx.State == SomeState, then xx.DoSomething().
Try-Parse: 使用TryParse()类的方法而不是Parse()类的方法。当我们选择向上层调用汇报异常时,我们需要考虑是否有标准异常能够准确表达这个业务错误,而不是盲目把当前异常类型向上传递,或者避免制造新的异常概念。最常见的例子应该是,在输入非法参数应该返回标准的参数异常(如C#里的ArgumentationException),而不是重新定义的另一种异常。
代码不需要也不该追求“重新定义”。许多代码可读性问题,往往就是因为“重新定义”造成的。
什么时候需要创建用户自定义的异常类型(user-defined exception)?
当标准异常无法有效表示业务错误需求时,我们便需要按业务需要创建自定义的异常类型。下文简称用户异常,以区别标准异常。
然而,这并不是说越是写具体业务代码,越是需要需要创建用户异常类型。反倒越是接触具体业务,越是不可能创建用户异常,但使用用户异常的机会更多。因为创建用户异常类型,多出现在业务框架中。业务框架抽象了业务的关键流程和重要模块,自然也需要抽象常见的流程错误的处理,那便需要定义用户异常,供业务代码使用。用户异常也多见于库(library),因为库是特定业务能力的封装,自然需要以异常的方式表示特定业务能力执行中可能出现的业务错误。
异常处理多是为了及早暴露问题,而不是掩盖问题
当处理代码中可能出现的异常,我们不能为了虚伪的健壮性而盲目掩盖所有的异常。当代码的执行无法从异常中恢复,我们应该如实去向调用者汇报异常;这时候我们能做的只是确保抛出的异常能够让调用者准确理解错误,并执行相应的业务错误处理逻辑。 这解释了为什么最佳实践建议捕获异常的代码需要指定具体的异常类型,而不能捕获通用异常(catch general exception)。错误不能及早发现,它可能会带来更严重的后果,或者让我们更难调试问题的原因。比如遇到应用启动初始化的异常,我们往往需要阻止应用成功启动,而不是任由应用正常启动,否则错误的初始化下的应用可能会做出一些我们难以理解的行为。
如何写出正确的异常处理代码?
我认为没什么特殊的技巧,只要你写代码的时候能回答以下问题就可以了
- 我的代码提供的功能是什么?可能出现的的错误是什么?
- 可能出现的错误应该用什么异常表示?
- 有哪些异常我是可以避免的或可以通过处理恢复执行的?
- 有哪些异常我是无法恢复执行的?而且我真的需要捕获它们吗?
- 当向上汇报异常时,我返回的异常是否能让上层业务调用者理解并正确处理这个错误?
来个“分享、点赞、在看”吧?
想了解叻道?请看此处!
感谢你的关注!