Go 学习之 error 篇

1 | 基础概念

// error 是个接口
type error interface {
  Error() string
}
// errorString 是 errors 包的私有类型
type errorString struct {
  s string
}

2 | 创建方法

  • 创建 error 的方法
    • errors.New()
    • fmt.Errorf() 格式化形式
package errors

fun New(text string) error {
  return &errorString{text}
}
---------------
package fmt

func Errorf(format string, a ...interface{}) error {
  return errors.New(Sprintf(format, a...))
}

3 | error 的用途(异常处理)

3.1 | 值比较,进行下一步处理

  • 与 nil 值 或 预定值 比较后,进行下一步处理
if err != nil {
  // something went wrong
}

// 标准库 os 包定义了一些常见的错误
// ErrPermission = errors.New("permission denied")
if err == os.ErrPermission {
  // permission denied
}

// 由于任何实现了 error 接口的类型均可以作为 error 来处理
// 所以 往往也会使用类型断言来检查 error
func AssertError(err error) {
  if e, ok := err.(*os.PathError); ok {
    fmt.Printf("it's an os.PathError, operation: %s, path: %s, msg: %v", e.Op, e.Path, e.Err)
  }
}

3.2 | 携带信息,向上层传递

  • 传递 error (在一个函数中收到 error 后,需要附加一些上下文信息,再向上层传递)
// 常见的添加附加上下文的方法是使用 fmt.Errorf() 方法
if err != nil {
  return fmt.Errorf("decompress %v:%v", name, err)
}

// 但这种方法 存在个问题 「error 信息会和附加的信息杂糅在一起」,不利于 error 判别
if fileName == "a.txt" {
  return fmt.Errorf("write file error: %v", os.ErrPermission)
}
// ErrPermission = errors.New("permission denied")
// 如上面,上下文信息 和 os.ErrPermisson 信息「杂糅在一起」,因此无法判别 err 是否是 os.ErrPermission 值了
// 也就是无法使用下面的判别
if err == os.Errpermission {
  fmt.Printf("permission denied")
}
  • 为了解决『杂糅』问题,采用『自定义 error 类型』的方法,将上下文信息和 error 信息分开存放
    • 但是判别错误时,必须要使用『类型断言』,才能进行后续处理
// 如上文提到的 os.PathError
type PathError struct {
  Op string // 上下文
  Path string // 上下文
  Err error // 原 error
}

// 必须要使用类型断言,才知道是哪种「自定义 error 类型」,才能继续处理
if e, ok := err.(*os.PathError); ok && e.Err == os.ErrPermission {
  fmt.Printf("permission denied")
}

3.3 | 小结

  • 携带上下文信息,向上传递时,可以使用 fmt.Errorf() 方法
  • 但 fmt.Errorf() 的最大问题是:将上下文信息 和 error 原始信息 「杂糅」了(可以理解为组合了一个新 error,丢弃了原 error)
  • 因此要对新 error 处理,必须要先定义个「新 error」类型,之后使用「断言」,才能进行后续处理

4 | 链式 error

链式 error 的诞生(Go 1.13),就是为了解决上述问题(杂糅,自定义 error,类型断言等)

type wrapError struct {

​ msg string // 存储上下文 和 err.Error()

​ err error // 存储原 error

}

链式 error 的理解:error 在函数间的传递,上下文信息好像链条把各个 error 连接起来。(其实通过上述结构体就大概可以明白,下层的 warpError 会将本身的 msg 和 err 组合成新error,传递到上层,作为上层 wrapError 中的 err,然后上层函数再添加附加信息到 msg 中,形成上层的 wrapError)

优化内容如下:

  • 新的 error 类型 wrapError
  • 增强了 fmt.Errorf() 以便通过 %w 动词创建 warpError
  • 引入了 errors.Unwrap() 以便拆解 warpError(这个就是去掉 msg 获取原 error)
  • 引入 errors.Is() 用于检查 error 链条中是否包含指定的错误值(因为是多个 error 连接起来,检测是否携带某个 error 时使用)
  • 引入 errors.As() 用于检查 error 链条中是否包含特定的错误类型,,若有,则转换为相应的类型(这个是比较,是否存在某种 error 类型,因为 error 有自定义类型)
  • 代码理解
type wrapError struct {
	msg string // 存储上下文 和 err.Error()
	err error // 存储原 error
}

func (e *wrapError) Error() string {
  return e.msg
}

func (e *wrapError) Unwrap() error {
  return e.err
}

// Unwrap(),若把 error 比作一件衣服,fmt.Errorf()(使用 %w)就好比给 error 增加一件外套,而 Unwrap 则是脱掉外套
func Unwrap(err error) error {
 	// 检查是否实现了 Unwrap 函数
  u, ok := err.(interface{
    Unwarp() error
  })
  // 没有实现 Unwrap 函数,不支持 Unwrap
  if !ok {
    return nil
  }
  return u.Unwrap()
}

// errors.Is() 用于检查特定的 error 链中是否包含指定的 error 值
// 逐层拆解参数 err,并与参数 target 比较
func Is(err, target error) bool {
  for {
    if err == target {
      return true
    }
    // 自定义的 error 类型如果实现了 Is() 方法,则会资源 Is() 方法
    if x, ok := err.(interface {
      Is(error) bool
    }); ok && x.Is(target) {
      return true
    }
    // 逐层拆解
    if err = Unwrap(err); err == nil{
      return false
    }
  }
}

// errors.As() 用于从一个 error 链中查找是否有指定的类型出现,若有,则把 error 转换成该类型
func As(err error, target interface{}) bool {
  val := reflectlite.ValueOf(target)
  typ := val.Type()
  tagetType := typ.Elem()
  for err != nil {
    // 若类型匹配,则将 err 复制
    if reflectlite.TypeOf(err).AssignableTo(targetType) {
      val.Elem().Set(reflectlite.Valueof(err))
      return true
    }
    // 如果 err 实现了 As() 方法,那么也会咨询 As()
    if x, ok := err.(interface{
      As(interface()) bool
    }); ok&&x.As(target) {
      return true
    }
    // 继续拆解
    err = Unwrap(err)
  }
}
  • 规范
1. fmt.Errorf() 函数每次只能接受一个 %w 动词
// 下面是错误示范
wrapError := fmt.Errorf("some context: %w, %w", err, err)

2. %w 只匹配 error 参数
// 因为格式动词 %w 专用于生成链式的 error,所以 fmt.Errorf() 函数只能匹配实现了 error 接口的参数
// 下面是错误示范
wrapError := fmt.Errorf("some context: %w","some thing")
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值