go语言宏定义 go语言 %v( 二 )


个人觉得这种设计模式本质上还是 C error code 模式 。
这种流派则是充分使用了 “error 是一个 interface” 的特性,重新自定义一个 error 类型 。一方面是用不同的类型来表示不同的错误分类,另一方面则能够实现对于同一错误类型,能够给调用方提供更佳详尽的信息 。举个例子,我们可以定义多个不同的错误类型如下:
对于调用方,则通过以下代码来判断不同的错误:
这种模式,一方面可以透传底层错误,另一方面又可以添加自定义的信息 。但对于调用方而言,灾难在于如果要判断某一个错误的具体类型,只能用 strings.Contains() 来实现,而错误的具体描述文字是不可靠的,同一类型的信息可能会有不同的表达;而在 fmt.Errorf 的过程中,各个业务添加的额外信息也可能会有不同的文字,这带来了极大的不可靠性,提高了模块之间的耦合度 。
在 go 1.13 版本发布之后 , 针对 fmt.Errorf 增加了 wraping 功能,并在 errors 包中添加了 Is() 和 As() 函数 。关于这个模式的原理和使用已经有很多文章了 , 本文就不再赘述 。
这个功能,合并并改造了前文的所谓 “== 流派” 和 “fmt.Errorf” 流派,统一使用 errors.Is() 函数;此外,也算是官方对类型断言流派的认可(专门用 As() 函数来支持) 。
在实际应用中 , 函数/模块透传错误时,应该采用 Go 的 error wrapping 模式,也就是 fmt.Errorf() 配合 %w 使用,业务方可以放心地添加自己的错误信息,只要调用方统一采用 errors.Is() 和 errors.As() 即可 。
服务/系统层面的错误信息返回,大部分协议都可以看成是 code - message 模式或者是其变体:
这种模式的特点是:code 是给程序代码使用的,代码判断这是一个什么类型的错误 , 进入相应的分支处理;而 message 是给人看的,程序可以以某种形式抛出或者记录这个错误信息 , 供用户查看 。
在这一层面有什么问题呢?code for computer,message for user , 好像挺好的 。
但有时候,我们可能会收到用户/客户反馈一个问题:“XXX 报错了,帮忙看看什么问题?” 。用户看不懂我们的错误提示吗?
在笔者的经验中 , 我们在使用 code - message 机制的时候,特别是业务初期,难以避免的是前后端的设计文案没能完整地覆盖所有的错误用例 , 或者是错误极其罕见 。因此当出现错误时,提示暧昧不清(甚至是直接提示错误信息),导致用户从错误信息中找到解决方案
在这种情况下,尽量覆盖所有错误路径肯定是最完美的方法 。不过在做到这一点之前 , 码农们往往有下面的解决方案:
既要隐藏信息,又要暴露信息,我可以摔盘子吗……
这里,笔者从日益普及的短信验证码有了个灵感——人的短期记忆对 4 个字符还是比较强的,因此我们可以考虑把错误代码缩短到 4 个字符——不区分大小写,因为如果人在记忆时还要记录大小写的话 , 难度会增加不少 。
怎么用 4 个字符表示尽量多的数据呢?数字+字母总共有 36 个字符 , 理论上使用 4 位 36 进制可以表示 36x36x36x36 = 1679616 个值 。因此我们只要找到一个针对错误信息字符串的哈希算法,把输出值限制在 1679616 范围内就行了 。
这里我采用的是 MD5 作为例子 。MD5 的输出是 128 位,理论上我可以取 MD5 的输出,模 1679616 就可以得到一个简易的结果 。实际上为了减少除法运算,我采用的是取高 20 位(0xFFFFF)的简易方式(20 位二进制的最大值为 1048575),然后将这个数字转成 36 进制的字符串输出 。

推荐阅读