在 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)
}
}
```