1. 异常流程
在确定产品功能的时候,人们提到更多的是,该产品应该怎样表现,
而实际上,产品所涉及的异常流程是否清晰,
才是提高功能可靠性的关键。
考虑如下一个简单的功能,
点击页面中的按钮,发起一个ajax请求,后端读取数据库返回相应的查询结果。
我们可以把它划分为三个环节,
(1)前端发起ajax请求给后端
(2)后端接受到请求,调用数据库查询服务
(3)查询数据库,返回相应的查询结果
线性系统的可靠性是每个系统组件的可靠性的乘积。
——《持续集成》
因此,假设以上每个环节的可靠性是 90%,那么整个系统的可靠性,则将只有 72.9%。
如果整个系统包含 100 个环节呢?可靠性就只剩下 0.0027% 了!!
因此,如果在系统层面承诺具有高可靠性,就得在每个环节上下足功夫。
2. 异常在所难免
在计算机科学中,健壮性(robustness)指的是,
一个计算机系统在执行过程中处理错误,
以及算法在遭遇输入、运算等异常时继续正常运行的能力。
要想提高代码的健壮性,我认为首先应该改变我们对错误的认知习惯。
我们应该认为,异常是在所难免的,
而剩下的问题是,都有哪些异常,以及如何处理它们。
有一个类似相同的论断,来自于《Reactive Design Patterns》
The question therefore is not if a failure occurs but only when or how often.
我们在这个基础上考虑问题。
3. 如何提供可靠的服务
人们通常认为,提供可靠的服务,就是不发生错误,
我认为这是不太恰当的。
因为作为底层服务,在出现某些错误的情况下,我们实在不应该替用户拿主意。
因此重要的事情,不是吞掉异常让服务看起来可靠,
而是,考虑用何种方式将错误呈现给用户,
这需要我们站在用户的角度考虑问题。
当考虑了异常之后,接口所传递的知识就在无形中被扩充了,
接口实际上包含了在不同情况下(正常/异常),应该返回什么结果。
3.1 反模式:出错消息
返回一段出错消息,是一种最常见的不为用户考虑的反模式,
因为用户不得不解析这段消息来确定究竟发生了什么错误。
这种情况用户一般也不会解析它,
而反馈给更上层的用户也是不合理的,
因此,这种消息最多保留到了日志中,或者干脆被直接忽略了。
为了让消息无歧义,更好的办法是返回错误的类型(或者是错误的统一编码),
它们会作为接口文档的一部分提供给用户,
并由用户决定处理方式。
3.2 反模式:不一致
比无法区分错误类型更好一点,但是同样有问题的报错方式就是,不一致的展示错误,
某些接口通过抛异常来报错,
另外一些接口统一捕获了异常,通过一个错误标志位来报错。
无疑这在某种程度上加大了用户代码的复杂度,
而且对于用户来说,他们也无法区分一个异常到底是接口的编写者已知的还是未知的。
用户无法确认一个使用错误标志位来报错的接口,会不会抛异常。
人们的确痛恨不一致性,但是却没有找到问题的症结所在,
让接口表现一致,是一种经常被推进,但实际上是一种过于理想化的解决方案。
在这一点上,我认为问题的症结在于知识没有被明确的传达给用户。
例如,如果我们在某个地方明确表明,“通过错误标志位来报错的接口一定不会抛异常”,
那么即使某些接口采用了不一致的报错方式,
我们仍然可以从容的处理了。
因此,不一致并没有问题,有问题的是歧义性。
4. 传递知识
经过上文的例证,我们看到接口所传递的知识比接口所提供的功能一样重要。
为了提供健壮的接口,那就得从一开始告诉用户,不能这样使用。
当然,寄希望于用户不会这样使用是没作用的。
墨菲定律表明,凡是可能出错的事,迟早会出错。
因此,除了上文讨论如何提供功能之外,
我们还要对用户,以及我们的依赖方进行管理。
4.1 用户管理
至少应该在某个地方明确表明,接口总共包含了哪些使用方式,
每一种使用方式,预期会得到怎样的结果,
越明确越好。
什么时候会出现异常,接口会以什么形式反馈出来。
例如,如果用户传入的参数不符合业务逻辑上的约束,接口会怎样表现,
接口在调用它的依赖方时出现了错误,应该如何处理。
这些都应该明确的向用户表明,
而不是,让用户假定接口应当采用何种处理方式。
4.2 依赖管理
我们要知道有哪些依赖,然后再仔细挖掘被依赖方所隐藏的信息,
被依赖方所采用的报错方式,可能并不是我们所期望的,
这其实并没有问题,问题是我们要有能力区分它们。
上文我们也看到了,很多项目都在“让依赖方进行修改”方面做出了无谓的努力,
这可能意味着我们在依赖管理方面做的还不够友好。
实际上,我们并不需要一个“规范的”接口,
但是我们需要一个具有“明确含义”的接口。
结语
我们来感受一下异常作为一种知识,是如何通过接口来传递的,
它如同接口的正常返回值一样。
可靠性的关键可能不在于表面上它不会出现错误,
而是在于,每一种可能出现的错误,都被恰当的责任人进行了处理。
当我们调用一个接口,它并没有抛异常,而是返回了一个Null
,
而且在多种不同的情况下,它都返回Null
,
这种接口简直是可笑的。
我们不得不联系接口的设计者,请他帮忙看看,到底发生了什么事情。
这真是一种极大的资源浪费。
这只是不良接口的一种表现形式而已,
所以重要的还是对异常流程,以及异常中所隐藏的知识有所察觉。
梁惠王曰:“寡人之于国也,尽心焉耳矣。河内凶,则移其民于河东,移其粟于河内。河东凶亦然。察邻国之政,无如寡人之用心者。邻国之民不加少,寡人之民不加多,何也?”
孟子对曰:“王好战,请以战喻。填然鼓之,兵刃既接,弃甲曳兵而走,或百步而后止,或五十步而后止。以五十步笑百步,则何如?”
曰:“不可。直不百步耳,是亦走也。”
曰:“王如知此,则无望民之多于邻国也。“