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")