偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

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

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

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

之前寫(xiě)過(guò)幾篇關(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è)庫(kù)。

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

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

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

首先我們?cè)購(gòu)?fù)述一遍,Go?通過(guò)error類(lèi)型的值表示程序里的錯(cuò)誤。

error?類(lèi)型是一個(gè)內(nèi)建接口類(lè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)用者通過(guò)測(cè)試error?值是否是nil來(lái)進(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表示失敗。

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

type MyError struct {
Sql string
Param string
Err error
}

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

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

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

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

所以針對(duì)自定義的錯(cuò)誤類(lèi)型,我們也要讓他變成一個(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ù)通過(guò)錯(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ò)誤信息的輸出上看沒(méi)什么問(wèn)題,但是深層次的問(wèn)題很明顯,我們丟失了原來(lái)的err?,因?yàn)樗呀?jīng)被我們的fmt.Errorf函數(shù)轉(zhuǎn)成一個(gè)新的字符串了。

基于這個(gè)背景,很多開(kāi)源三方庫(kù)提供了錯(cuò)誤包裝、追加錯(cuò)誤調(diào)用棧等功能,用的最多的就是"github.com/pkg/errors"這個(gè)庫(kù),提供了下面幾個(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ò)誤包裝的支持,不過(guò)并沒(méi)有提供什么Wrap?函數(shù),而是擴(kuò)展了fmt.Errorf?函數(shù),加了一個(gè)%w來(lái)生成一個(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。

先來(lái)聊聊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ù),類(lèi)似這樣

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

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

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

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

errors.Is

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

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

這樣我們就可以通過(guò)判斷來(lái)做一些事情。但是現(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ò)誤鏈中的話(huà),那么也返回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

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

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

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

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

As? 函數(shù)所做的就是遍歷錯(cuò)誤的嵌套鏈,從里面找到類(lèi)型符合的error,然后把這個(gè)error賦給target參數(shù),這樣我們?cè)诔绦蚶锞涂梢允褂棉D(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í)我一直沒(méi)更新過(guò)來(lái),這里簡(jiǎn)單做個(gè)梳理。

責(zé)任編輯:武曉燕 來(lái)源: 網(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語(yǔ)言方式

2014-11-17 10:05:12

Go語(yǔ)言

2021-04-29 09:02:44

語(yǔ)言Go 處理

2021-09-27 10:04:03

Go程序處理

2021-09-27 15:33:48

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

2024-10-16 12:23:55

技巧Spring驗(yàn)證

2025-06-06 06:45:54

2022-09-05 08:55:15

Go2提案語(yǔ)法

2025-06-30 09:49:11

2025-03-31 00:29:44

2021-09-13 07:53:31

Go錯(cuò)誤處理

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多協(xié)程并發(fā)

2025-02-08 09:57:20

2021-04-14 07:08:14

Nodejs錯(cuò)誤處理
點(diǎn)贊
收藏

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