🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
在 Go 语言中,错误是可以预期的,并且不是非常严重,不会影响程序的运行。对于这类问题,可以用返回错误给调用者的方法,让调用者自己决定如何处理。 在 Go 语言中,错误是通过内置的 error 接口表示的。它非常简单,只有一个 Error 方法用来返回具体的错误信息,如下面的代码所示: **error 接口** ``` type error interface { Error() string } ``` 示例: ``` func main() { i,err:=strconv.Atoi("a") if err!=nil { fmt.Println(err) }else { fmt.Println(i) } } ``` 打印错误信息: ``` strconv.Atoi: parsing "a": invalid syntax ``` **自定义错误信息** ``` func main() { result, err := div(9,0) if err == nil{ fmt.Println(result) }else{ fmt.Println(err) } } func div(m, n int) (int, error){ if n == 0{ return 0, errors.New("被除数不能为0") } return m/n, nil } ``` **自定义 error** 上面返回错误信息的方式只能传递一个字符串,也就是携带的信息只有字符串,如果想要携带更多信息(比如错误码信息)该怎么办呢?这个时候就需要自定义 error 。 自定义 error 其实就是先自定义一个新类型,比如结构体,然后让这个类型实现 error 接口,如下面的代码所示: ``` type commonError struct { errorCode int //错误码 errorMsg string //错误信息 } func (err *commonError) Error() string{ return err.errorMsg } ``` 有了自定义的 error,就可以使用它携带更多的信息,现在改造上面的例子,返回刚刚自定义的 commonError,如下所示: ``` func div(m, n int) (int, error){ if n == 0{ return 0, &CommonError{1, "被除数不能为0"} } return m/n, nil } ``` **error 断言** 有了自定义的 error,并且携带了更多的错误信息后,就可以使用这些信息了。你需要先把返回的 error 接口转换为自定义的错误类型。 ``` func main() { result, err := div(9,0) if ce, ok := err.(*CommonError); !ok{ fmt.Println(result) }else{ fmt.Println("错误码为:", ce.ErrorCode, "错误信息为:", ce.ErrorMsg) } } ``` 如果返回的 ok 为 true,说明 error 断言成功,正确返回了 *commonError 类型的变量 ce,所以就可以像示例中一样使用变量 ce 的 errorCode 和 errorMsg 字段信息了。 **错误嵌套** 基于已经存在的 error 再生成一个 error 比如调用一个函数,返回了一个错误信息 error,在不想丢失这个 error 的情况下,又想添加一些额外信息返回新的 error,可以使用自定义 struct 实现 ``` type MyError struct { err error msg string } ``` 这个结构体有两个字段,其中 error 类型的 err 字段用于存放已存在的 error,string 类型的 msg 字段用于存放新的错误信息,这种方式就是 error 的嵌套。 现在让 MyError 这个 struct 实现 error 接口,然后在初始化 MyError 的时候传递存在的 error 和新的错误信息,如下面的代码所示: ``` func (e *MyError) Error() string { return e.err.Error() + e.msg } func main() { //err是一个存在的错误,可以从另外一个函数返回 newErr := MyError{err, "数据上传问题"} } ``` 这种方式可以满足我们的需求,但是非常烦琐,因为既要定义新的类型还要实现 error 接口。所以从 Go 语言 1.13 版本开始,Go 标准库新增了 Error Wrapping 功能,让我们可以基于一个存在的 error 生成新的 error,并且可以保留原 error 信息,如下面的代码所示: ```golang e := errors.New("原始错误e") w := fmt.Errorf("wrap了一个错误:%w", e) fmt.Println(w) // wrap了一个错误:原始错误e ``` **errors.Unwrap 函数** 既然 error 可以包裹嵌套生成一个新的 error,那么也可以被解开,即通过 errors.Unwrap 函数得到被嵌套的 error。 Go 语言提供了 errors.Unwrap 用于获取被嵌套的 error,比如以上例子中的错误变量 w ,就可以对它进行 unwrap,获取被嵌套的原始错误 e。 ```golang fmt.Println(errors.Unwrap(w)) // 原始错误e ``` **errors.Is** 有了 Error Wrapping 后,原来用的判断两个 error 是不是同一个 error 的方法失效了,比如 Go 语言标准库经常用到的如下代码中的方式: ``` if err == os.ErrExist ``` 为什么会出现这种情况呢?由于 Go 语言的 Error Wrapping 功能,令人不知道返回的 err 是否被嵌套,又嵌套了几层? 于是 Go 语言为我们提供了 errors.Is 函数,用来判断两个 error 是否是同一个,如下所示: ``` func Is(err, target error) bool ``` 以上就是errors.Is 函数的定义,可以解释为: - 如果 err 和 target 是同一个,那么返回 true。 - 如果 err 是一个 wrapping error,target 也包含在这个嵌套 error 链中的话,也返回 true。 ``` func main() { e := errors.New("原始错误e") w := fmt.Errorf("wrap了一个错误:%w", e) fmt.Println(errors.Is(w,e)) } ``` **errors.As** 同样的原因,有了 error 嵌套后,error 断言也不能用了,因为你不知道一个 error 是否被嵌套,又嵌套了几层。所以 Go 语言为解决这个问题提供了 errors.As 函数,比如前面 error 断言的例子,可以使用 errors.As 函数重写,效果是一样的,如下面的代码所示: ``` func main() { result, err := div(9, 0) var ce *CommonError if errors.As(err,&ce){ fmt.Println("错误代码为:",ce.ErrorCode,",错误信息为:",ce.ErrorMsg) } else { fmt.Println(result) } } ```