成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Go版本大于1.13,程序里這樣做錯誤處理才地道

開發 前端
這篇文章主要是更新一下Error處理在Go 1.13以后新增的功能點,以前的文章介紹的更多的還是使用"pkg/errors"那個包的方式,主要是前兩年以前公司用的Go版本一直是1.12,所以這部分知識我一直沒更新過來,這里簡單做個梳理。

大家好,這里是每周都在陪你進步的網管。

之前寫過幾篇關于 Go 錯誤處理的文章,發現文章里不少知識點都有點落伍了,比如Go在1.13后對錯誤處理增加了一些支持,最大的變化就是支持了錯誤包裝(Error Wrapping),以前想要在調用鏈路的函數里包裝錯誤都是用"github.com/pkg/errors"這個庫。

Go 在2019年發布的Go1.13版本也采納了錯誤包裝,并且還提供了幾個很有用的工具函數讓我們能更好地使用包裝錯誤。這篇文章就來主要說一下這方面的知識點,不過開始我們還是再次強調一下使用 Go Error 的誤區,避免我們從其他語言切換過來時給自己后面挖坑。

自定義錯誤要實現error接口

這一條估計很多人都知道,但是文章開頭開始先從這個慣例開始,因為我以前待過一個PHP轉Go的研發團隊,可能大家一開始都不太會,才有了這種錯誤的使用方式。

首先我們再復述一遍,Go?通過error類型的值表示程序里的錯誤。

error?類型是一個內建接口類型,該接口只規定了一個返回字符串值的Error方法。

type error interface {
Error() string
}

Go?程序的函數經常會返回一個error值

package strconv

func Atoi(s string) (int, error) {
....
}

調用者通過測試error?值是否是nil來進行錯誤處理。

i, err := strconv.Atoi("42")
if err != nil {
fmt.Printf("couldn't convert number: %v\n", err)
return
}
fmt.Println("Converted integer:", i)

error為nil?時表示成功;非nil的error表示失敗。

說完 Go? 里 error 最基本的使用方式后,接下來說項目里的自定義錯誤類型。假如項目在 Dao 層定義了一個這樣的錯誤類型來記錄數據庫查詢錯誤。

type MyError struct {
Sql string
Param string
Err error
}

假如,這個自定義的MyError?不去實現error?接口,Dao 層里的函數返回的都是MyError的話。

func FindUserRowByPhoneMyError(userId int) (user User, MyError error) {
......
}

那么使用這些 Dao 函數的代碼邏輯層都得引入dao.MyError?這個額外的類型。有人會說,我把MyError?定義在公共包里,所有代碼邏輯層、Dao 層都用這個common.MyError總沒啥問題了吧。

使用上乍一看沒什么問題,但其實最大的問題就是不兼容、不符合Go語言對錯誤的接口約束,就沒法對自定義錯誤類型使用Go對error提供的其他功能了,比如說后面要介紹的錯誤包裝。

所以針對自定義的錯誤類型,我們也要讓他變成一個真正的Go error,方法就是讓它實現error接口定義的方法。

func (e *MyError) Error() string {
return fmt.Sprintf("sql: %s, params: %s, err: %s", e.Sql, e.Param, e.Err.Error())
}

包裝錯誤

在現實的程序應用里,一個邏輯往往要經多多層函數的調用才能完成,那在程序里我們的建議Error Handling 盡量留給上層的調用函數做,中間和底層的函數通過錯誤包裝把自己要記的錯誤信息附加再原始錯誤上再返回給外層函數。

比如像下面這樣:

func doAnotherThing() error {
return errors.New("error doing another thing")
}

func doSomething() error {
err := doAnotherThing()
return fmt.Errorf("error doing something: %v", err)
}

func main() {
err := doSomething()
fmt.Println(err)
}

這段代碼從打印錯誤信息的輸出上看沒什么問題,但是深層次的問題很明顯,我們丟失了原來的err?,因為它已經被我們的fmt.Errorf函數轉成一個新的字符串了。

基于這個背景,很多開源三方庫提供了錯誤包裝、追加錯誤調用棧等功能,用的最多的就是"github.com/pkg/errors"這個庫,提供了下面幾個主要的包裝錯誤的功能。

//只附加新的信息
func WithMessage(err error, message string) error

//只附加調用堆棧信息
func WithStack(err error) error

//同時附加堆棧和信息
func Wrap(err error, message string) error

Go官方在2019年發布1.13?版本,自己也增加了對錯誤包裝的支持,不過并沒有提供什么Wrap?函數,而是擴展了fmt.Errorf?函數,加了一個%w來生成一個包裝錯誤。

e := errors.New("原始錯誤")
w := fmt.Errorf("外面包了一個錯誤%w", e)

Go1.13?引入了包裝錯誤后,同時為內置的errors?包添加了3個函數,分別是Unwrap、Is和As。

先來聊聊Unwrap,顧名思義,它的功能就是為了獲取到包裝錯誤里那個被嵌套的error。

func Unwrap(err error) error {
//先判斷是否是wrapping error
u, ok := err.(interface {
Unwrap() error
})
//如果不是,返回nil
if !ok {
return nil
}
//否則則調用該error的Unwrap方法返回被嵌套的error
return u.Unwrap()
}

這里需要注意的是,嵌套可以有很多層,我們調用一次errors.Unwrap?函數只能返回往里一層的error?,如果想獲取更里面的,需要調用多次errors.Unwrap?函數。最終如果一個error?不是warpping error,那么返回的是nil。

如果想得到最原始的error,建議自己封裝個工具函數,類似這樣

func Cause(err error) error {
for err != nil {
err = errors.Unwrap(err)
}
return err
}

對于我們文章開頭定義的那個自定義錯誤MyError?想要把它變成可包裝的Error的話,還需要實現一個Unwrap()方法。

func (e *MyError) Unwrap() error { return e.Err }

有了包裝錯誤后,像具體某種錯誤的判斷和錯誤的類型轉換也得需要跟進改一下才行。這就是errors?包在1.13?后新增的另外兩個工具函數Is和As的作用。接下來我們一個個來說。

errors.Is

在Go 1.13之前沒有包裝錯誤的時候,程序里要判斷是不是同一個error可以直接簡單粗暴的:

if err == os.ErrNotExists {
......
}

這樣我們就可以通過判斷來做一些事情。但是現在有了包裝錯誤后這樣辦法就不完美的,因為你根本不知道返回的這個err?是不是一個嵌套的error,嵌套了幾層。所以基于這種情況,Go為我們提供了errors.Is函數。

func Is(err, target error) bool

如果err?和目標錯誤target?是同一個,那么返回true。

如果err? 是一個包裝錯誤,目標錯誤target?也包含在這個嵌套錯誤鏈中的話,那么也返回true。

下面是一個使用errors.Is判斷是否是同一錯誤的例子。


var ErrDivideByZero = errors.New("divide by zero")

func Divide(a, b int) (int, error) {
if b == 0 {
return 0, ErrDivideByZero
}
return a / b, nil
}

func main() {
a, b := 10, 0
result, err := Divide(a, b)
if err != nil {
switch {
case errors.Is(err, ErrDivideByZero):
fmt.Println("divide zero error")
default:
fmt.Printf("unexpected division error: %+v\n", err)
}
return
}

fmt.Printf("%d / %d = %d\n", a, b, result)
}

errors.As

同樣在沒有包裝錯誤前,我們要把error 轉換為一個具體類型的error,一般都是使用類型斷言或者 type switch,其實也就是類型斷言。

if pathErr, ok := err.(*os.PathError); ok {
fmt.Println(pathErr.Path)
}

但是有了包裝錯誤之后,返回的err可能是已經被嵌套了,這種方式就不能用了,所以Go為我們在errors?包里提供了As函數。

func As(err error, target interface{}) bool

As? 函數所做的就是遍歷錯誤的嵌套鏈,從里面找到類型符合的error,然后把這個error賦給target參數,這樣我們在程序里就可以使用轉換后的target了,因為這里有賦值,所以target必須是一個指針,這個也算是Go內置包里的一個慣例了,像json.Unmarshal也是這樣。

所以把上面的例子用As 函數實現就變成了醬嬸:

var pathErr *os.PathError
if errors.As(err, pathErr) {
fmt.Println(pathErr.Path)
}

總結

這篇文章主要是更新一下Error處理在Go 1.13以后新增的功能點,以前的文章介紹的更多的還是使用"pkg/errors"那個包的方式,主要是前兩年以前公司用的Go版本一直是1.12,所以這部分知識我一直沒更新過來,這里簡單做個梳理。

責任編輯:武曉燕 來源: 網管叨bi叨
相關推薦

2023-03-10 08:48:29

2025-03-31 08:57:25

Go程序性能

2024-06-05 08:47:20

Go語言方式

2014-11-17 10:05:12

Go語言

2021-04-29 09:02:44

語言Go 處理

2021-09-27 10:04:03

Go程序處理

2021-09-27 15:33:48

Go 開發技術

2024-10-16 12:23:55

技巧Spring驗證

2025-06-06 06:45:54

2022-09-05 08:55:15

Go2提案語法

2025-06-30 09:49:11

2025-03-31 00:29:44

2021-09-13 07:53:31

Go錯誤處理

2025-02-06 08:54:45

gockGoHTTP

2024-03-27 08:18:02

Spring映射HTML

2020-12-17 06:25:05

Gopanic 模式

2023-10-26 15:49:53

Go日志

2021-09-27 23:28:29

Go多協程并發

2025-02-08 09:57:20

2021-04-14 07:08:14

Nodejs錯誤處理
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 天天干,夜夜操 | 国产免费一级片 | 欧美极品在线播放 | 色婷婷综合久久久中字幕精品久久 | 色.com| 久草网视频 | 亚洲国产欧美一区二区三区久久 | 激情a| 日韩一区二区三区视频 | 高清久久久 | 亚洲一区二区精品视频 | 精品久久久久久久久久久 | 一级片在线观看 | 国产欧美精品一区二区三区 | 日韩欧美在线视频 | 午夜精品在线观看 | 国产性网 | 婷婷久久综合 | 国产高清在线精品 | 国产美女精品 | 欧美黄色大片在线观看 | 色黄视频在线 | 国产真实乱对白精彩久久小说 | 欧美一级久久精品 | 超碰最新在线 | 亚洲精品久久久久久久久久久久久 | 精国产品一区二区三区四季综 | 亚洲成人精品 | a久久| 在线免费av电影 | 一区二区三区精品在线视频 | 免费观看一级毛片 | www.久草| 久久精品国产一区二区三区不卡 | 99一区二区| 国产综合久久久久久鬼色 | 日韩一区二区黄色片 | 国产精品视频一二三区 | 成人免费视频网站在线观看 | 一区二区三区精品在线视频 | 小h片免费观看久久久久 |