[Engineering] 设计模式奏鸣曲(三):系统边界处的异常处理

[Engineering] 设计模式奏鸣曲(三):系统边界处的异常处理_第1张图片

1. 异常流程

在确定产品功能的时候,人们提到更多的是,该产品应该怎样表现,
而实际上,产品所涉及的异常流程是否清晰,
才是提高功能可靠性的关键。

考虑如下一个简单的功能,
点击页面中的按钮,发起一个ajax请求,后端读取数据库返回相应的查询结果。

我们可以把它划分为三个环节,
(1)前端发起ajax请求给后端
(2)后端接受到请求,调用数据库查询服务
(3)查询数据库,返回相应的查询结果

[Engineering] 设计模式奏鸣曲(三):系统边界处的异常处理_第2张图片

线性系统的可靠性是每个系统组件的可靠性的乘积。
——《持续集成》

因此,假设以上每个环节的可靠性是 90%,那么整个系统的可靠性,则将只有 72.9%。
如果整个系统包含 100 个环节呢?可靠性就只剩下 0.0027% 了!!

因此,如果在系统层面承诺具有高可靠性,就得在每个环节上下足功夫。

2. 异常在所难免

在计算机科学中,健壮性(robustness)指的是,
一个计算机系统在执行过程中处理错误,
以及算法在遭遇输入、运算等异常时继续正常运行的能力。

要想提高代码的健壮性,我认为首先应该改变我们对错误的认知习惯。

[Engineering] 设计模式奏鸣曲(三):系统边界处的异常处理_第3张图片

我们应该认为,异常是在所难免的
而剩下的问题是,都有哪些异常,以及如何处理它们。

有一个类似相同的论断,来自于《Reactive Design Patterns》

The question therefore is not if a failure occurs but only when or how often.

我们在这个基础上考虑问题。

3. 如何提供可靠的服务

人们通常认为,提供可靠的服务,就是不发生错误,
我认为这是不太恰当的。

因为作为底层服务,在出现某些错误的情况下,我们实在不应该替用户拿主意

[Engineering] 设计模式奏鸣曲(三):系统边界处的异常处理_第4张图片

因此重要的事情,不是吞掉异常让服务看起来可靠,
而是,考虑用何种方式将错误呈现给用户
这需要我们站在用户的角度考虑问题。

当考虑了异常之后,接口所传递的知识就在无形中被扩充了,
接口实际上包含了在不同情况下(正常/异常),应该返回什么结果。

3.1 反模式:出错消息

[Engineering] 设计模式奏鸣曲(三):系统边界处的异常处理_第5张图片

返回一段出错消息,是一种最常见的不为用户考虑的反模式
因为用户不得不解析这段消息来确定究竟发生了什么错误。

这种情况用户一般也不会解析它,
而反馈给更上层的用户也是不合理的,
因此,这种消息最多保留到了日志中,或者干脆被直接忽略了。

为了让消息无歧义,更好的办法是返回错误的类型(或者是错误的统一编码),
它们会作为接口文档的一部分提供给用户,
并由用户决定处理方式。

3.2 反模式:不一致

比无法区分错误类型更好一点,但是同样有问题的报错方式就是,不一致的展示错误,
某些接口通过抛异常来报错,
另外一些接口统一捕获了异常,通过一个错误标志位来报错。

无疑这在某种程度上加大了用户代码的复杂度,
而且对于用户来说,他们也无法区分一个异常到底是接口的编写者已知的还是未知的
用户无法确认一个使用错误标志位来报错的接口,会不会抛异常。

[Engineering] 设计模式奏鸣曲(三):系统边界处的异常处理_第6张图片

人们的确痛恨不一致性,但是却没有找到问题的症结所在,
让接口表现一致,是一种经常被推进,但实际上是一种过于理想化的解决方案。
在这一点上,我认为问题的症结在于知识没有被明确的传达给用户

例如,如果我们在某个地方明确表明,“通过错误标志位来报错的接口一定不会抛异常”,
那么即使某些接口采用了不一致的报错方式,
我们仍然可以从容的处理了。

因此,不一致并没有问题,有问题的是歧义性

4. 传递知识

经过上文的例证,我们看到接口所传递的知识比接口所提供的功能一样重要。
为了提供健壮的接口,那就得从一开始告诉用户,不能这样使用

[Engineering] 设计模式奏鸣曲(三):系统边界处的异常处理_第7张图片

当然,寄希望于用户不会这样使用是没作用的。
墨菲定律表明,凡是可能出错的事,迟早会出错。

因此,除了上文讨论如何提供功能之外,
我们还要对用户,以及我们的依赖方进行管理。

4.1 用户管理

至少应该在某个地方明确表明,接口总共包含了哪些使用方式,
每一种使用方式,预期会得到怎样的结果,
明确越好。

[Engineering] 设计模式奏鸣曲(三):系统边界处的异常处理_第8张图片

什么时候会出现异常,接口会以什么形式反馈出来。
例如,如果用户传入的参数不符合业务逻辑上的约束,接口会怎样表现,
接口在调用它的依赖方时出现了错误,应该如何处理。

这些都应该明确的向用户表明,
而不是,让用户假定接口应当采用何种处理方式。

4.2 依赖管理

[Engineering] 设计模式奏鸣曲(三):系统边界处的异常处理_第9张图片

我们要知道有哪些依赖,然后再仔细挖掘被依赖方所隐藏的信息,
被依赖方所采用的报错方式,可能并不是我们所期望的,
这其实并没有问题,问题是我们要有能力区分它们。

上文我们也看到了,很多项目都在“让依赖方进行修改”方面做出了无谓的努力,
这可能意味着我们在依赖管理方面做的还不够友好。

实际上,我们并不需要一个“规范的”接口,
但是我们需要一个具有“明确含义”的接口。

结语

我们来感受一下异常作为一种知识,是如何通过接口来传递的,
它如同接口的正常返回值一样。

可靠性的关键可能不在于表面上它不会出现错误,
而是在于,每一种可能出现的错误,都被恰当的责任人进行了处理。

[Engineering] 设计模式奏鸣曲(三):系统边界处的异常处理_第10张图片

当我们调用一个接口,它并没有抛异常,而是返回了一个Null
而且在多种不同的情况下,它都返回Null
这种接口简直是可笑的

我们不得不联系接口的设计者,请他帮忙看看,到底发生了什么事情。
这真是一种极大的资源浪费。

这只是不良接口的一种表现形式而已,
所以重要的还是对异常流程,以及异常中所隐藏的知识有所察觉。

梁惠王曰:“寡人之于国也,尽心焉耳矣。河内凶,则移其民于河东,移其粟于河内。河东凶亦然。察邻国之政,无如寡人之用心者。邻国之民不加少,寡人之民不加多,何也?”

孟子对曰:“王好战,请以战喻。填然鼓之,兵刃既接,弃甲曳兵而走,或百步而后止,或五十步而后止。以五十步笑百步,则何如?”

曰:“不可。直不百步耳,是亦走也。”

曰:“王如知此,则无望民之多于邻国也。“

你可能感兴趣的:([Engineering] 设计模式奏鸣曲(三):系统边界处的异常处理)