自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

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

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

大家好,這里是每周都在陪你進(jìn)步的網(wǎng)管。

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

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

自定義錯(cuò)誤要實(shí)現(xiàn)error接口

這一條估計(jì)很多人都知道,但是文章開頭開始先從這個(gè)慣例開始,因?yàn)槲乙郧按^一個(gè)PHP轉(zhuǎn)Go的研發(fā)團(tuán)隊(duì),可能大家一開始都不太會(huì),才有了這種錯(cuò)誤的使用方式。

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

error?類型是一個(gè)內(nèi)建接口類型,該接口只規(guī)定了一個(gè)返回字符串值的Error方法。

type error interface {
Error() string
}

Go?程序的函數(shù)經(jīng)常會(huì)返回一個(gè)error值

package strconv

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

調(diào)用者通過測試error?值是否是nil來進(jìn)行錯(cuò)誤處理。

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?時(shí)表示成功;非nil的error表示失敗。

說完 Go? 里 error 最基本的使用方式后,接下來說項(xiàng)目里的自定義錯(cuò)誤類型。假如項(xiàng)目在 Dao 層定義了一個(gè)這樣的錯(cuò)誤類型來記錄數(shù)據(jù)庫查詢錯(cuò)誤。

type MyError struct {
Sql string
Param string
Err error
}

假如,這個(gè)自定義的MyError?不去實(shí)現(xiàn)error?接口,Dao 層里的函數(shù)返回的都是MyError的話。

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

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

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

所以針對(duì)自定義的錯(cuò)誤類型,我們也要讓他變成一個(gè)真正的Go error,方法就是讓它實(shí)現(xiàn)error接口定義的方法。

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

包裝錯(cuò)誤

在現(xiàn)實(shí)的程序應(yīng)用里,一個(gè)邏輯往往要經(jīng)多多層函數(shù)的調(diào)用才能完成,那在程序里我們的建議Error Handling 盡量留給上層的調(diào)用函數(shù)做,中間和底層的函數(shù)通過錯(cuò)誤包裝把自己要記的錯(cuò)誤信息附加再原始錯(cuò)誤上再返回給外層函數(shù)。

比如像下面這樣:

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

這段代碼從打印錯(cuò)誤信息的輸出上看沒什么問題,但是深層次的問題很明顯,我們丟失了原來的err?,因?yàn)樗呀?jīng)被我們的fmt.Errorf函數(shù)轉(zhuǎn)成一個(gè)新的字符串了。

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

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

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

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

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

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

Go1.13?引入了包裝錯(cuò)誤后,同時(shí)為內(nèi)置的errors?包添加了3個(gè)函數(shù),分別是Unwrap、Is和As。

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

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

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

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

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

對(duì)于我們文章開頭定義的那個(gè)自定義錯(cuò)誤MyError?想要把它變成可包裝的Error的話,還需要實(shí)現(xiàn)一個(gè)Unwrap()方法。

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

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

errors.Is

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

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

這樣我們就可以通過判斷來做一些事情。但是現(xiàn)在有了包裝錯(cuò)誤后這樣辦法就不完美的,因?yàn)槟愀静恢婪祷氐倪@個(gè)err?是不是一個(gè)嵌套的error,嵌套了幾層。所以基于這種情況,Go為我們提供了errors.Is函數(shù)。

func Is(err, target error) bool

如果err?和目標(biāo)錯(cuò)誤target?是同一個(gè),那么返回true。

如果err? 是一個(gè)包裝錯(cuò)誤,目標(biāo)錯(cuò)誤target?也包含在這個(gè)嵌套錯(cuò)誤鏈中的話,那么也返回true。

下面是一個(gè)使用errors.Is判斷是否是同一錯(cuò)誤的例子。


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

同樣在沒有包裝錯(cuò)誤前,我們要把error 轉(zhuǎn)換為一個(gè)具體類型的error,一般都是使用類型斷言或者 type switch,其實(shí)也就是類型斷言。

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

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

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

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

所以把上面的例子用As 函數(shù)實(shí)現(xiàn)就變成了醬嬸:

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

總結(jié)

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

責(zé)任編輯:武曉燕 來源: 網(wǎng)管叨bi叨
相關(guān)推薦

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 15:33:48

Go 開發(fā)技術(shù)

2021-09-27 10:04:03

Go程序處理

2024-10-16 12:23:55

技巧Spring驗(yàn)證

2025-03-31 00:29:44

2021-09-13 07:53:31

Go錯(cuò)誤處理

2022-09-05 08:55:15

Go2提案語法

2025-02-06 08:54:45

gockGoHTTP

2024-03-27 08:18:02

Spring映射HTML

2023-10-26 15:49:53

Go日志

2021-09-27 23:28:29

Go多協(xié)程并發(fā)

2020-12-17 06:25:05

Gopanic 模式

2025-02-08 09:57:20

2021-04-14 07:08:14

Nodejs錯(cuò)誤處理

2022-07-13 08:53:28

函數(shù)Go語言

2025-02-24 09:30:15

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)