Error槽点分析:

  • Error处理不够优雅 割裂正常业务逻辑,降低了代码可读性。
  • if err ≠ nil 处理 大量重复出现
  • 简单的 return err 无法适用于各种场景,返回自定义错误信息。
 

GO作者对error的设计理念

error 只是函数返回值之一,地位与其他返回值等价。
  • 对error处理 就是对普通返回值的处理
 

由于官方包的一些问题,诞生了比较实用的民间包

errors 包使用简介:

  1. errors.New(”创建一个error”)
    1.  
  1. 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)
 
  1. 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.Errnoos.PathError,当你只关心os.PathError错误类型,就可以用As方法了。
 
  1. errors.Wrap
Wrap方法用来包装底层错误,增加上下文文本信息并附加调用栈。一般用于包装对第三方代码(标准库或第三方库)的调用。当输出时fmt.Printf或者fmtSprinitf使用%+v则会输出堆栈信息(具体原理可见源码)。
notion image
 
  1. errors.WithMessage
WithMessage方法仅增加上下文文本信息,不附加调用栈。如果确定错误已被Wrap过或不关心调用栈,可以使用此方法。注意:不要反复Wrap,会导致调用栈重复
 
  1. errors.Cause
Cause方法用来判断底层错误。让我们可以获得最根本的错误原因
 
  1. 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(哨兵错误),然后在调用的时候外部包可以直接对比变量进行判定,在标准库当中大量的使用了这种方式。 但这样暴露出去也有可能被人修改了。