詳解golang函數(shù)多返回值錯(cuò)誤處理與error類(lèi)型
一、error 類(lèi)型與錯(cuò)誤值構(gòu)造
1.1 Error 接口介紹
在Go語(yǔ)言中,error
類(lèi)型是一個(gè)接口類(lèi)型,通常用于表示錯(cuò)誤。它定義如下:
type error interface { Error() string }
error
接口只有一個(gè)方法,即 Error()
方法,該方法返回一個(gè)描述錯(cuò)誤的字符串。這意味著任何實(shí)現(xiàn)了 Error()
方法的類(lèi)型都可以被用作錯(cuò)誤類(lèi)型。通常,Go程序中的函數(shù)在遇到錯(cuò)誤時(shí)會(huì)返回一個(gè) error
類(lèi)型的值,以便調(diào)用方可以處理或記錄錯(cuò)誤信息。
1.2 構(gòu)造錯(cuò)誤值的方法
1.2.1 使用errors包
Go 語(yǔ)言的設(shè)計(jì)者提供了兩種方便 Go
開(kāi)發(fā)者構(gòu)造錯(cuò)誤值的方法: errors.New
和 fmt.Errorf
。
errors.New()
函數(shù)是創(chuàng)建最簡(jiǎn)單的錯(cuò)誤值的方法,它只包含一個(gè)錯(cuò)誤消息字符串。這個(gè)方法適用于創(chuàng)建簡(jiǎn)單的錯(cuò)誤值。fmt.Errorf()
函數(shù)允許你構(gòu)造一個(gè)格式化的錯(cuò)誤消息,類(lèi)似于fmt.Printf()
函數(shù)。這對(duì)于需要構(gòu)建更復(fù)雜的錯(cuò)誤消息時(shí)非常有用。
使用這兩種方法,我們可以輕松構(gòu)造出一個(gè)滿(mǎn)足 error
接口的錯(cuò)誤值,就像下面代碼這樣:
err := errors.New("your first demo error") errWithCtx = fmt.Errorf("index %d is out of bounds", i)
這兩種方法實(shí)際上返回的是同一個(gè)實(shí)現(xiàn)了 error 接口的類(lèi)型的實(shí)例,這個(gè)未導(dǎo)出的類(lèi)型就是 errors.errorString
,它的定義是這樣的:
// $GOROOT/src/errors/errors.go type errorString struct { s string } func (e *errorString) Error() string { return e.s }
大多數(shù)情況下,使用這兩種方法構(gòu)建的錯(cuò)誤值就可以滿(mǎn)足我們的需求了。但我們也要看到,雖然這兩種構(gòu)建錯(cuò)誤值的方法很方便,但它們給錯(cuò)誤處理者提供的錯(cuò)誤上下文(Error Context)只限于以字符串形式呈現(xiàn)的信息,也就是 Error 方法返回的信息。
1.2.2 自定義錯(cuò)誤類(lèi)型
在一些場(chǎng)景下,錯(cuò)誤處理者需要從錯(cuò)誤值中提取出更多信息,幫助他選擇錯(cuò)誤處理路徑,顯然這兩種方法就不能滿(mǎn)足了。這個(gè)時(shí)候,我們可以自定義錯(cuò)誤類(lèi)型來(lái)滿(mǎn)足這一需求。以下是一個(gè)示例:
package main import "fmt" // 自定義錯(cuò)誤類(lèi)型 type MyError struct { ErrorCode int ErrorMessage string } // 實(shí)現(xiàn) error 接口的 Error 方法 func (e MyError) Error() string { return fmt.Sprintf("錯(cuò)誤 %d: %s", e.ErrorCode, e.ErrorMessage) } func someFunction() error { // 創(chuàng)建自定義錯(cuò)誤值 err := MyError{ ErrorCode: 404, ErrorMessage: "未找到", } return err } func main() { // 調(diào)用 someFunction,返回自定義錯(cuò)誤值 err := someFunction() // 打印錯(cuò)誤信息 fmt.Println("錯(cuò)誤:", err) }
我們?cè)賮?lái)看一個(gè)例子,比如:標(biāo)準(zhǔn)庫(kù)中的 net
包就定義了一種攜帶額外錯(cuò)誤上下文的錯(cuò)誤類(lèi)型:
// $GOROOT/src/net/net.go type OpError struct { Op string Net string Source Addr Addr Addr Err error }
這樣,錯(cuò)誤處理者就可以根據(jù)這個(gè)類(lèi)型的錯(cuò)誤值提供的額外上下文信息,比如 Op、Net、Source 等,做出錯(cuò)誤處理路徑的選擇,比如下面標(biāo)準(zhǔn)庫(kù)中的代碼:
// $GOROOT/src/net/http/server.go func isCommonNetReadError(err error) bool { if err == io.EOF { return true } if neterr, ok := err.(net.Error); ok && neterr.Timeout() { return true } if oe, ok := err.(*net.OpError); ok && oe.Op == "read" { return true } return false }
我們看到,上面這段代碼利用類(lèi)型斷言(Type Assertion
),判斷 error
類(lèi)型變量 err 的動(dòng)態(tài)類(lèi)型是否為 *net.OpError
或 net.Error。如果 err
的動(dòng)態(tài)類(lèi)型是 *net.OpError
,那么類(lèi)型斷言就會(huì)返回這個(gè)動(dòng)態(tài)類(lèi)型的值(存儲(chǔ)在 oe
中),代碼就可以通過(guò)判斷它的 Op
字段是否為"read
"來(lái)判斷它是否為 CommonNetRead
類(lèi)型的錯(cuò)誤。
二、error 類(lèi)型的好處
2.1 第一點(diǎn):統(tǒng)一了錯(cuò)誤類(lèi)型
如果不同開(kāi)發(fā)者的代碼、不同項(xiàng)目中的代碼,甚至標(biāo)準(zhǔn)庫(kù)中的代碼,都統(tǒng)一以 error
接口變量的形式呈現(xiàn)錯(cuò)誤類(lèi)型,就能在提升代碼可讀性的同時(shí),還更容易形成統(tǒng)一的錯(cuò)誤處理策略。
2.2 第二點(diǎn):錯(cuò)誤是值
我們構(gòu)造的錯(cuò)誤都是值,也就是說(shuō),即便賦值給 error 這個(gè)接口類(lèi)型變量,我們也可以像整型值那樣對(duì)錯(cuò)誤做“==”和“!=”的邏輯比較,函數(shù)調(diào)用者檢視錯(cuò)誤時(shí)的體驗(yàn)保持不變。
由于 error 是一個(gè)接口類(lèi)型,默認(rèn)零值為nil
。所以我們通常將調(diào)用函數(shù)返回的錯(cuò)誤與nil
進(jìn)行比較,以此來(lái)判斷函數(shù)是否返回錯(cuò)誤。如果返回的錯(cuò)誤為 nil
,則表示函數(shù)執(zhí)行成功,否則表示出現(xiàn)了錯(cuò)誤。這種約定使得錯(cuò)誤處理變得一致和直觀。例如你會(huì)經(jīng)??吹筋?lèi)似下面的錯(cuò)誤判斷代碼。
func someFunction() error { // 模擬一個(gè)出錯(cuò)的情況 return errors.New("這是一個(gè)錯(cuò)誤") } func main() { err := someFunction() if err != nil { fmt.Println("函數(shù)執(zhí)行失敗,錯(cuò)誤信息:", err) } else { fmt.Println("函數(shù)執(zhí)行成功") } }
2.3 第三點(diǎn):易擴(kuò)展,支持自定義錯(cuò)誤上下文
雖然錯(cuò)誤以 error 接口變量的形式統(tǒng)一呈現(xiàn),但我們很容易通過(guò)自定義錯(cuò)誤類(lèi)型來(lái)擴(kuò)展我們的錯(cuò)誤上下文,就像前面的 Go 標(biāo)準(zhǔn)庫(kù)的 OpError
類(lèi)型那樣。
error 接口是錯(cuò)誤值的提供者與錯(cuò)誤值的檢視者之間的契約。error 接口的實(shí)現(xiàn)者負(fù)責(zé)提供錯(cuò)誤上下文,供負(fù)責(zé)錯(cuò)誤處理的代碼使用。這種錯(cuò)誤具體上下文與作為錯(cuò)誤值類(lèi)型的 error 接口類(lèi)型的解耦,也體現(xiàn)了 Go 組合設(shè)計(jì)哲學(xué)中“正交”的理念。
三、Go 錯(cuò)誤處理的慣用策略
3.1 策略一:透明錯(cuò)誤處理策略
簡(jiǎn)單來(lái)說(shuō),Go 語(yǔ)言中的錯(cuò)誤處理,就是根據(jù)函數(shù) / 方法返回的 error
類(lèi)型變量中攜帶的錯(cuò)誤值信息做決策,并選擇后續(xù)代碼執(zhí)行路徑的過(guò)程。
這樣,最簡(jiǎn)單的錯(cuò)誤策略莫過(guò)于完全不關(guān)心返回錯(cuò)誤值攜帶的具體上下文信息,只要發(fā)生錯(cuò)誤就進(jìn)入唯一的錯(cuò)誤處理執(zhí)行路徑,比如下面這段代碼:
err := doSomething() if err != nil { // 不關(guān)心err變量底層錯(cuò)誤值所攜帶的具體上下文信息 // 執(zhí)行簡(jiǎn)單錯(cuò)誤處理邏輯并返回 ... ... return err }
這是 Go 語(yǔ)言中最常見(jiàn)的錯(cuò)誤處理策略,80% 以上的 Go 錯(cuò)誤處理情形都可以歸類(lèi)到這種策略下。在這種策略下,由于錯(cuò)誤處理方并不關(guān)心錯(cuò)誤值的上下文,所以錯(cuò)誤值的構(gòu)造方(如上面的函數(shù) doSomething)可以直接使用 Go 標(biāo)準(zhǔn)庫(kù)提供的兩個(gè)基本錯(cuò)誤值構(gòu)造方法 errors.New 和 fmt.Errorf 來(lái)構(gòu)造錯(cuò)誤值,就像下面這樣:
func doSomething(...) error { ... ... return errors.New("some error occurred") }
這樣構(gòu)造出的錯(cuò)誤值代表的上下文信息,對(duì)錯(cuò)誤處理方是透明的,因此這種策略稱(chēng)為“透明錯(cuò)誤處理策略”。在錯(cuò)誤處理方不關(guān)心錯(cuò)誤值上下文的前提下,透明錯(cuò)誤處理策略能最大程度地減少錯(cuò)誤處理方與錯(cuò)誤值構(gòu)造方之間的耦合關(guān)系。
3.2 策略二:“哨兵”錯(cuò)誤處理策略
當(dāng)錯(cuò)誤處理方不能只根據(jù)“透明的錯(cuò)誤值”就做出錯(cuò)誤處理路徑選取的情況下,錯(cuò)誤處理方會(huì)嘗試對(duì)返回的錯(cuò)誤值進(jìn)行檢視,于是就有可能出現(xiàn)下面代碼中的反模式:
data, err := b.Peek(1) if err != nil { switch err.Error() { case "bufio: negative count": // ... ... return case "bufio: buffer full": // ... ... return case "bufio: invalid use of UnreadByte": // ... ... return default: // ... ... return } }
簡(jiǎn)單來(lái)說(shuō),反模式就是,錯(cuò)誤處理方以透明錯(cuò)誤值所能提供的唯一上下文信息(描述錯(cuò)誤的字符串),作為錯(cuò)誤處理路徑選擇的依據(jù)。但這種“反模式”會(huì)造成嚴(yán)重的隱式耦合。這也就意味著,錯(cuò)誤值構(gòu)造方不經(jīng)意間的一次錯(cuò)誤描述字符串的改動(dòng),都會(huì)造成錯(cuò)誤處理方處理行為的變化,并且這種通過(guò)字符串比較的方式,對(duì)錯(cuò)誤值進(jìn)行檢視的性能也很差。
那這有什么辦法嗎?Go 標(biāo)準(zhǔn)庫(kù)采用了定義導(dǎo)出的(Exported)“哨兵”錯(cuò)誤值的方式,來(lái)輔助錯(cuò)誤處理方檢視(inspect)錯(cuò)誤值并做出錯(cuò)誤處理分支的決策,比如下面的 bufio 包中定義的“哨兵錯(cuò)誤”:
// $GOROOT/src/bufio/bufio.go var ( ErrInvalidUnreadByte = errors.New("bufio: invalid use of UnreadByte") ErrInvalidUnreadRune = errors.New("bufio: invalid use of UnreadRune") ErrBufferFull = errors.New("bufio: buffer full") ErrNegativeCount = errors.New("bufio: negative count") )
下面的代碼片段利用了上面的哨兵錯(cuò)誤,進(jìn)行錯(cuò)誤處理分支的決策:
data, err := b.Peek(1) if err != nil { switch err { case bufio.ErrNegativeCount: // ... ... return case bufio.ErrBufferFull: // ... ... return case bufio.ErrInvalidUnreadByte: // ... ... return default: // ... ... return } }
你可以看到,一般“哨兵”錯(cuò)誤值變量以 ErrXXX 格式命名。和透明錯(cuò)誤策略相比,“哨兵”策略讓錯(cuò)誤處理方在有檢視錯(cuò)誤值的需求時(shí)候,可以“有的放矢”。
不過(guò),對(duì)于 API 的開(kāi)發(fā)者而言,暴露“哨兵”錯(cuò)誤值也意味著這些錯(cuò)誤值和包的公共函數(shù) / 方法一起成為了 API 的一部分。一旦發(fā)布出去,開(kāi)發(fā)者就要對(duì)它進(jìn)行很好的維護(hù)。而“哨兵”錯(cuò)誤值也讓使用這些值的錯(cuò)誤處理方對(duì)它產(chǎn)生了依賴(lài)。
從 Go 1.13 版本開(kāi)始,標(biāo)準(zhǔn)庫(kù) errors 包提供了 Is 函數(shù)用于錯(cuò)誤處理方對(duì)錯(cuò)誤值的檢視。Is 函數(shù)類(lèi)似于把一個(gè) error 類(lèi)型變量與“哨兵”錯(cuò)誤值進(jìn)行比較,比如下面代碼:
// 類(lèi)似 if err == ErrOutOfBounds{ … } if errors.Is(err, ErrOutOfBounds) { // 越界的錯(cuò)誤處理 }
不同的是,如果 error 類(lèi)型變量的底層錯(cuò)誤值是一個(gè)包裝錯(cuò)誤(Wrapped Error),errors.Is 方法會(huì)沿著該包裝錯(cuò)誤所在錯(cuò)誤鏈(Error Chain),與鏈上所有被包裝的錯(cuò)誤(Wrapped Error)進(jìn)行比較,直至找到一個(gè)匹配的錯(cuò)誤為止。下面是 Is 函數(shù)應(yīng)用的一個(gè)例子:
var ErrSentinel = errors.New("the underlying sentinel error") func main() { err1 := fmt.Errorf("wrap sentinel: %w", ErrSentinel) err2 := fmt.Errorf("wrap err1: %w", err1) println(err2 == ErrSentinel) //false if errors.Is(err2, ErrSentinel) { println("err2 is ErrSentinel") return } println("err2 is not ErrSentinel") }
在這個(gè)例子中,我們通過(guò) fmt.Errorf
函數(shù),并且使用%w
創(chuàng)建包裝錯(cuò)誤變量 err1 和 err2,其中 err1 實(shí)現(xiàn)了對(duì) ErrSentinel 這個(gè)“哨兵錯(cuò)誤值”的包裝,而 err2 又對(duì) err1 進(jìn)行了包裝,這樣就形成了一條錯(cuò)誤鏈。位于錯(cuò)誤鏈最上層的是 err2,位于最底層的是 ErrSentinel。之后,我們?cè)俜謩e通過(guò)值比較和 errors.Is 這兩種方法,判斷 err2 與 ErrSentinel 的關(guān)系。運(yùn)行上述代碼,我們會(huì)看到如下結(jié)果:
false
err2 is ErrSentinel
我們看到,通過(guò)比較操作符對(duì) err2 與 ErrSentinel 進(jìn)行比較后,我們發(fā)現(xiàn)這二者并不相同。而 errors.Is 函數(shù)則會(huì)沿著 err2 所在錯(cuò)誤鏈,向下找到被包裝到最底層的“哨兵”錯(cuò)誤值ErrSentinel
。
如果你使用的是 Go 1.13 及后續(xù)版本,建議你盡量使用errors.Is
方法去檢視某個(gè)錯(cuò)誤值是否就是某個(gè)預(yù)期錯(cuò)誤值,或者包裝了某個(gè)特定的“哨兵”錯(cuò)誤值。
3.3 策略三:錯(cuò)誤值類(lèi)型檢視策略
上面我們看到,基于 Go 標(biāo)準(zhǔn)庫(kù)提供的錯(cuò)誤值構(gòu)造方法構(gòu)造的“哨兵”錯(cuò)誤值,除了讓錯(cuò)誤處理方可以“有的放矢”的進(jìn)行值比較之外,并沒(méi)有提供其他有效的錯(cuò)誤上下文信息。那如果遇到錯(cuò)誤處理方需要錯(cuò)誤值提供更多的“錯(cuò)誤上下文”的情況,上面這些錯(cuò)誤處理策略和錯(cuò)誤值構(gòu)造方式都無(wú)法滿(mǎn)足。
這種情況下,我們需要通過(guò)自定義錯(cuò)誤類(lèi)型的構(gòu)造錯(cuò)誤值的方式,來(lái)提供更多的“錯(cuò)誤上下文”信息。并且,由于錯(cuò)誤值都通過(guò) error 接口變量統(tǒng)一呈現(xiàn),要得到底層錯(cuò)誤類(lèi)型攜帶的錯(cuò)誤上下文信息,錯(cuò)誤處理方需要使用 Go 提供的類(lèi)型斷言機(jī)制(Type Assertion)或類(lèi)型選擇機(jī)制(Type Switch),這種錯(cuò)誤處理方式,我稱(chēng)之為錯(cuò)誤值類(lèi)型檢視策略。
我們來(lái)看一個(gè)標(biāo)準(zhǔn)庫(kù)中的例子加深下理解,這個(gè) json 包中自定義了一個(gè) UnmarshalTypeError
的錯(cuò)誤類(lèi)型:
// $GOROOT/src/encoding/json/decode.go type UnmarshalTypeError struct { Value string Type reflect.Type Offset int64 Struct string Field string }
錯(cuò)誤處理方可以通過(guò)錯(cuò)誤類(lèi)型檢視策略,獲得更多錯(cuò)誤值的錯(cuò)誤上下文信息,下面就是利用這一策略的json
包的一個(gè)方法的實(shí)現(xiàn):
// $GOROOT/src/encoding/json/decode.go func (d *decodeState) addErrorContext(err error) error { if d.errorContext.Struct != nil || len(d.errorContext.FieldStack) > 0 { switch err := err.(type) { case *UnmarshalTypeError: err.Struct = d.errorContext.Struct.Name() err.Field = strings.Join(d.errorContext.FieldStack, ".") return err } } return err }
我們看到,這段代碼通過(guò)類(lèi)型 switch 語(yǔ)句得到了 err 變量代表的動(dòng)態(tài)類(lèi)型和值,然后在匹配的 case 分支中利用錯(cuò)誤上下文信息進(jìn)行處理。
這里,一般自定義導(dǎo)出的錯(cuò)誤類(lèi)型以 XXXError
的形式命名。和“哨兵”錯(cuò)誤處理策略一樣,錯(cuò)誤值類(lèi)型檢視策略,由于暴露了自定義的錯(cuò)誤類(lèi)型給錯(cuò)誤處理方,因此這些錯(cuò)誤類(lèi)型也和包的公共函數(shù) / 方法一起,成為了 API 的一部分。一旦發(fā)布出去,開(kāi)發(fā)者就要對(duì)它們進(jìn)行很好的維護(hù)。而它們也讓使用這些類(lèi)型進(jìn)行檢視的錯(cuò)誤處理方對(duì)其產(chǎn)生了依賴(lài)。
從 Go 1.13 版本開(kāi)始,標(biāo)準(zhǔn)庫(kù) errors 包提供了As函數(shù)給錯(cuò)誤處理方檢視錯(cuò)誤值。As函數(shù)類(lèi)似于通過(guò)類(lèi)型斷言判斷一個(gè) error 類(lèi)型變量是否為特定的自定義錯(cuò)誤類(lèi)型,如下面代碼所示:
// 類(lèi)似 if e, ok := err.(*MyError); ok { … } var e *MyError if errors.As(err, &e) { // 如果err類(lèi)型為*MyError,變量e將被設(shè)置為對(duì)應(yīng)的錯(cuò)誤值 }
不同的是,如果 error 類(lèi)型變量的動(dòng)態(tài)錯(cuò)誤值是一個(gè)包裝錯(cuò)誤,errors.As
函數(shù)會(huì)沿著該包裝錯(cuò)誤所在錯(cuò)誤鏈,與鏈上所有被包裝的錯(cuò)誤的類(lèi)型進(jìn)行比較,直至找到一個(gè)匹配的錯(cuò)誤類(lèi)型,就像 errors.Is
函數(shù)那樣。下面是As
函數(shù)應(yīng)用的一個(gè)例子:
type MyError struct { e string } func (e *MyError) Error() string { return e.e } func main() { var err = &MyError{"MyError error demo"} err1 := fmt.Errorf("wrap err: %w", err) err2 := fmt.Errorf("wrap err1: %w", err1) var e *MyError if errors.As(err2, &e) { println("MyError is on the chain of err2") println(e == err) return } println("MyError is not on the chain of err2") }
運(yùn)行上述代碼會(huì)得到:
MyError is on the chain of err2
true
我們看到,errors.As
函數(shù)沿著 err2
所在錯(cuò)誤鏈向下找到了被包裝到最深處的錯(cuò)誤值,并將 err2
與其類(lèi)型 * MyError
成功匹配。匹配成功后,errors.As 會(huì)將匹配到的錯(cuò)誤值存儲(chǔ)到 As 函數(shù)的第二個(gè)參數(shù)中,這也是為什么 println(e == err
)輸出 true
的原因。
如果你使用的是 Go 1.13 及后續(xù)版本,請(qǐng)盡量使用 errors.As方法去檢視某個(gè)錯(cuò)誤值是否是某自定義錯(cuò)誤類(lèi)型的實(shí)例。
3.4 策略四:錯(cuò)誤行為特征檢視策略
不知道你注意到?jīng)]有,在前面我們已經(jīng)講過(guò)的三種策略中,其實(shí)只有第一種策略,也就是“透明錯(cuò)誤處理策略”,有效降低了錯(cuò)誤的構(gòu)造方與錯(cuò)誤處理方兩者之間的耦合。雖然前面的策略二和策略三,都是我們實(shí)際編碼中有效的錯(cuò)誤處理策略,但其實(shí)使用這兩種策略的代碼,依然在錯(cuò)誤的構(gòu)造方與錯(cuò)誤處理方兩者之間建立了耦合。
那么除了“透明錯(cuò)誤處理策略”外,我們是否還有手段可以降低錯(cuò)誤處理方與錯(cuò)誤值構(gòu)造方的耦合呢?
在 Go 標(biāo)準(zhǔn)庫(kù)中,我們發(fā)現(xiàn)了這樣一種錯(cuò)誤處理方式:將某個(gè)包中的錯(cuò)誤類(lèi)型歸類(lèi),統(tǒng)一提取出一些公共的錯(cuò)誤行為特征,并將這些錯(cuò)誤行為特征放入一個(gè)公開(kāi)的接口類(lèi)型中。這種方式也被叫做錯(cuò)誤行為特征檢視策略。
以標(biāo)準(zhǔn)庫(kù)中的net
包為例,它將包內(nèi)的所有錯(cuò)誤類(lèi)型的公共行為特征抽象并放入 net.Error
這個(gè)接口中,如下面代碼:
// $GOROOT/src/net/net.go type Error interface { error Timeout() bool Temporary() bool }
我們看到,net.Error
接口包含兩個(gè)用于判斷錯(cuò)誤行為特征的方法:Timeout
用來(lái)判斷是否是超時(shí)(Timeout
)錯(cuò)誤,Temporary
用于判斷是否是臨時(shí)(Temporary
)錯(cuò)誤。
而錯(cuò)誤處理方只需要依賴(lài)這個(gè)公共接口,就可以檢視具體錯(cuò)誤值的錯(cuò)誤行為特征信息,并根據(jù)這些信息做出后續(xù)錯(cuò)誤處理分支選擇的決策。
這里,我們?cè)倏匆粋€(gè) http 包使用錯(cuò)誤行為特征檢視策略進(jìn)行錯(cuò)誤處理的例子,加深下理解:
// $GOROOT/src/net/http/server.go func (srv *Server) Serve(l net.Listener) error { ... ... for { rw, e := l.Accept() if e != nil { select { case <-srv.getDoneChan(): return ErrServerClosed default: } if ne, ok := e.(net.Error); ok && ne.Temporary() { // 注:這里對(duì)臨時(shí)性(temporary)錯(cuò)誤進(jìn)行處理 ... ... time.Sleep(tempDelay) continue } return e } ... } ... ... }
在上面代碼中,Accept
方法實(shí)際上返回的錯(cuò)誤類(lèi)型為 *OpError,它是 net
包中的一個(gè)自定義錯(cuò)誤類(lèi)型,它實(shí)現(xiàn)了錯(cuò)誤公共特征接口 net.Error
,如下代碼所示:
// $GOROOT/src/net/net.go type OpError struct { ... ... // Err is the error that occurred during the operation. Err error } type temporary interface { Temporary() bool } func (e *OpError) Temporary() bool { if ne, ok := e.Err.(*os.SyscallError); ok { t, ok := ne.Err.(temporary) return ok && t.Temporary() } t, ok := e.Err.(temporary) return ok && t.Temporary() }
因此,OpError 實(shí)例可以被錯(cuò)誤處理方通過(guò) net.Error 接口的方法,判斷它的行為是否滿(mǎn)足 Temporary 或 Timeout 特征。
四、總結(jié)
Go 語(yǔ)言統(tǒng)一錯(cuò)誤類(lèi)型為 error 接口類(lèi)型,并提供了多種快速構(gòu)建可賦值給 error 類(lèi)型的錯(cuò)誤值的函數(shù),包括 errors.New、fmt.Errorf 等,我們還講解了使用統(tǒng)一 error 作為錯(cuò)誤類(lèi)型的優(yōu)點(diǎn),你要深刻理解這一點(diǎn)。
基于 Go 錯(cuò)誤處理機(jī)制、統(tǒng)一的錯(cuò)誤值類(lèi)型以及錯(cuò)誤值構(gòu)造方法的基礎(chǔ)上,Go 語(yǔ)言形成了多種錯(cuò)誤處理的慣用策略,包括透明錯(cuò)誤處理策略、“哨兵”錯(cuò)誤處理策略、錯(cuò)誤值類(lèi)型檢視策略以及錯(cuò)誤行為特征檢視策略等。這些策略都有適用的場(chǎng)合,但沒(méi)有某種單一的錯(cuò)誤處理策略可以適合所有項(xiàng)目或所有場(chǎng)合。
在錯(cuò)誤處理策略選擇上,你可以參考以下:
- 請(qǐng)盡量使用“透明錯(cuò)誤”處理策略,降低錯(cuò)誤處理方與錯(cuò)誤值構(gòu)造方之間的耦合;
- 如果可以從眾多錯(cuò)誤類(lèi)型中提取公共的錯(cuò)誤行為特征,那么請(qǐng)盡量使用“錯(cuò)誤行為特征檢視策略”;
- 在上述兩種策略無(wú)法實(shí)施的情況下,再使用“哨兵”策略和“錯(cuò)誤值類(lèi)型檢視”策略;
- Go 1.13 及后續(xù)版本中,盡量用
errors.Is
和errors.As
函數(shù)替換原先的錯(cuò)誤檢視比較語(yǔ)句。
以上就是詳解golang函數(shù)多返回值錯(cuò)誤處理與error類(lèi)型的詳細(xì)內(nèi)容,更多關(guān)于golang錯(cuò)誤處理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- Go語(yǔ)言中的錯(cuò)誤處理最佳實(shí)踐詳解
- 重學(xué)Go語(yǔ)言之錯(cuò)誤處理與異常機(jī)制詳解
- Go語(yǔ)言中錯(cuò)誤處理的方式總結(jié)
- Go語(yǔ)言中實(shí)現(xiàn)完美錯(cuò)誤處理實(shí)踐分享
- Golang錯(cuò)誤處理方式異常與error
- Golang中的錯(cuò)誤處理深入分析
- Golang中的錯(cuò)誤處理的示例詳解
- Golang中的錯(cuò)誤處理的示例詳解
- Go 代碼規(guī)范錯(cuò)誤處理示例經(jīng)驗(yàn)總結(jié)
- Go?錯(cuò)誤處理實(shí)踐總結(jié)示例
- Go錯(cuò)誤處理的幾種方式
相關(guān)文章
詳解golang中?work與?module?的區(qū)別與聯(lián)系
Go?模塊通常由一個(gè)項(xiàng)目或庫(kù)組成,并包含一組隨后一起發(fā)布的?Go?包,Go?模塊通過(guò)允許用戶(hù)將項(xiàng)目代碼放在他們選擇的目錄中并為每個(gè)模塊指定依賴(lài)項(xiàng)的版本,解決了原始系統(tǒng)的許多問(wèn)題,本文將給大家介紹一下golang中?work與?module?的區(qū)別與聯(lián)系,需要的朋友可以參考下2023-09-09利用Go語(yǔ)言實(shí)現(xiàn)輕量級(jí)OpenLdap弱密碼檢測(cè)工具
這篇文章主要為大家詳細(xì)介紹了如何利用Go語(yǔ)言實(shí)現(xiàn)輕量級(jí)OpenLdap弱密碼檢測(cè)工具,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以嘗試一下2022-09-09Go Grpc Gateway兼容HTTP協(xié)議文檔自動(dòng)生成網(wǎng)關(guān)
這篇文章主要為大家介紹了Go Grpc Gateway兼容HTTP協(xié)議文檔自動(dòng)生成網(wǎng)關(guān)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06Go語(yǔ)言構(gòu)建流數(shù)據(jù)pipeline的示例詳解
Go的并發(fā)原語(yǔ)可以輕松構(gòu)建流數(shù)據(jù)管道,從而高效利用?I/O?和多個(gè)?CPU,?本文展示了此類(lèi)pipelines的示例,強(qiáng)調(diào)了操作失敗時(shí)出現(xiàn)的細(xì)微之處,并介紹了干凈地處理失敗的技術(shù),希望對(duì)大家有所幫助2024-02-02golang中結(jié)構(gòu)體嵌套接口的實(shí)現(xiàn)
本文主要介紹了golang中結(jié)構(gòu)體嵌套接口的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-04-04