Error槽点分析:
- Error处理不够优雅 割裂正常业务逻辑,降低了代码可读性。
if err ≠ nil
处理 大量重复出现
- 简单的
return err
无法适用于各种场景,返回自定义错误信息。
GO作者对error的设计理念
error 只是函数返回值之一,地位与其他返回值等价。
- 对error处理 就是对普通返回值的处理
由于官方包的一些问题,诞生了比较实用的民间包
errors 包使用简介:
errors.New(”创建一个error”)
errors.Is()
errors.Is(err error, target error) bool方法会解包所有err里包装的error,如果里面有任何一个解包后== target,则返回true 使用场景:希望判断err里面是否包含某个错,因为error可能会被包装很多层,比如经常我们的数据库报错了,那么上一层会再进行包装,一直到最上一层的话,可能err里面包含了很多信息那么我们需要判断是不是数据库错误了,就可以用这个方法了。
判断err 里是否包含某个err 比
if err ≠ nil
更精确,例如:errors.Is(err,redis.Nil)errors.As()
提取target 错误,提取某个错误
errors.As(err error, target interface(}) bool方法会解包所有err里包装的error,并且看是否能类型转换为target的类型,如果可以,则将换后的结果赋值到target,并返回true,否则false。target必须是指针使用场景:当我们只关心是否是某一类错误,例如有redis,mysql错误,但是我只关心是否是redis错误,此时就可以用这个方法了。
查看源码,你能发现os.Open
会返回有2种自定义error,比如syscall.Errno
,os.PathError
,当你只关心os.PathErro
r错误类型,就可以用As
方法了。
errors.Wrap
Wrap方法用来包装底层错误,增加上下文文本信息并附加调用栈。一般用于包装对第三方代码(标准库或第三方库)的调用。当输出时fmt.Printf
或者fmt
。Sprinitf
使用%+v则会输出堆栈信息(具体原理可见源码)。

errors.WithMessage
WithMessage方法仅增加上下文文本信息,不附加调用栈。如果确定错误已被Wrap过或不关心调用栈,可以使用此方法。注意:不要反复Wrap,会导致调用栈重复
errors.Cause
Cause方法用来判断底层错误。让我们可以获得最根本的错误原因
errors.
WithStack
如果不需要增加额外上下文信息,仅附加调用栈后返回,可以使用WithStack方法。它和 Wrap唯一的区别就是不能传入message。硬要说具体的使用场景,那就是针对官方error包返回的error进行类型用errors.WithStack
使其成为一个带堆栈的error
关于error的工程实践建议
- error应该是函数的最后一个返回值,当
error !=nil
时,函数的其他返回值是不可用的状态,不应该对其他返回值做任何期待
- 处理错误时应该时第一时间判断错误,当
if err!= nil
出现错误及时返回,使代码是一条流畅的直线,避免过多的嵌套。(快乐路径)
- 如果是调用应用程序的其他函数出现错误,请直接返回,如果需要携带信息,请使用
errors. WithMessage
- 如果是调用其他库(标准库、企业公共库、开源第三方库等)获取到错误时,请使用
errors.Wrap
添加堆栈信息,不要每个地方都是用errors.Wrap
只需要在错误第一次出现时进行errors.Wrap
即可
- 禁止每个出错的地方都打日志,只需要在进程的最开始的地方使用%+v进行统一打印或者不返回err时,例如
http/rpc
服务的中间件
- 错误判断使
errors.Is
行比较
- 对于业务错误,推荐在一个统一的地方创建一个错误字典,错误字典里面应该包含错误的 code,并且在日志中作为独立字段打印,方便做业务告警的判断,错误必须有清晰的错误文档(错误SDK使用的errorNo)
- 在业务中可以定一些包级别或者项目级别的,可以使用Sentinel Error(哨兵错误),然后在调用的时候外部包可以直接对比变量进行判定,在标准库当中大量的使用了这种方式。 但这样暴露出去也有可能被人修改了。
- 一些优雅的处理错误方法,可以参考Go官方博客Errors are values