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