type
status
date
slug
summary
tags
category
icon
password
一种优雅的Golang error设计模式 | YuanboShe’s Blog (pz1.top)里面介绍了base-error的错误处理方案,这篇介绍在中间件封装中更有用的链式调用,采用的是流水线的设计思想,可以大大简化中间件调用者的代码量。

回顾

在上篇的“base-error错误处理方案”中,强调了由于是基于现有语法,通过设计模式达到主动/集中error处理的目的,以解决调用者满屏if err != nil的苦恼。但也有局限性,很适合多种独立操作组合使用的中间件封装场景,但对开发者和调用者都有规范使用的要求,也不适合简单操作的场景,反而会增加工作量。不熟悉模式的适用场景而滥用模式只是自讨苦吃。
另外还强调一点,由于是调用者主动通过Err()获取出错信息,假如每个操作的错误返回都处理,还是和if err != nil的工作量一样,所以这里假定的前提是,很多错误返回是可以忽略和没必要处理的,集中处理后,通过大量的测试覆盖来找出那些需要处理的error,而忽略掉基本上不可能发生的error,从而减少工作量。

流水线模式与链式调用

链式调用的前提是流水线设计思想,想象整个调用链就像是一条流水线,里面每个节点的(函数/方法)操作都是流水线上的一段加工设备。这段加工设备的入口可以是入参,也可以是加工”方法“所绑定的对象本身,而出口只能是绑定了下一步操作的对象。
notion image
如上面图中的例子,通过NewXxx()或者InitAddr(t)函数对对象进行初始化,输出对象t,然后进行t对象上的Method1()加工,继续输出对象t,再进行t对象上的Method2()加工,依次加工,最后可以用t对象上的Err()获取错误信息,判断是否需要进行错误处理。
只要遵循流水线的规范,中间的加工方法可以输出任意绑定了下一步操作的对象。调用链最后一步操作随意,随后集中进行错误检查。对于中间件的调用者,可以任意组合这条流水线,大大简化代码量。
类似下面的例子:

注意事项

首先,集中式的错误处理是前提,这保障了error返回不会占用流水线中间操作的出口。
然后,每一步操作都必须保障对后续的操作没有副作用,即调用者无论链接多少操作,通过Err()获取错误信息时,都是最早出错的error,只要有error出现,后续操作都不会执行。这需要每个中间方法,也得按照流水线的设计思路编写。
如上面的例子,DoSomethingWithError()就像是流水线中间一段设备,p产品流入设备进行加工,首先检查p产品有没有error,有则丢出来不要进行加工,任意中间的加工环节也是如此,只要出现error就把p产品通过p.SetErr()丢出来。最后,这能够保障当使用者使用Err()主动处理错误时,得到的是最早出现的error,而且之后的所有加工操作都没有被执行。
最后,当使用者通过Err()检查出p产品的瑕疵,修复了问题后,如果还需要对p产品继续加工,那么必须通过SetErr(nil)手动将错误标记给清除,否则加工过程不会被执行。
base-error模式下的封装规范一种优雅的Golang error设计模式
Loading...