亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

詳解golang函數(shù)多返回值錯(cuò)誤處理與error類(lèi)型

 更新時(shí)間:2023年10月19日 08:21:08   作者:賈維斯Echo  
這篇文章主要為大家詳細(xì)介紹了golang中函數(shù)多返回值錯(cuò)誤處理與error類(lèi)型的相關(guān)知識(shí),文中的示例代碼簡(jiǎn)潔易懂,感興趣的小伙伴快跟隨小編一起學(xué)習(xí)吧

一、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)文章!

相關(guān)文章

  • golang 字符串拼接性能的對(duì)比分析

    golang 字符串拼接性能的對(duì)比分析

    這篇文章主要介紹了golang 字符串拼接性能的對(duì)比分析,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-12-12
  • 詳解golang中?work與?module?的區(qū)別與聯(liá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è)工具

    利用Go語(yǔ)言實(shí)現(xiàn)輕量級(jí)OpenLdap弱密碼檢測(cè)工具

    這篇文章主要為大家詳細(xì)介紹了如何利用Go語(yǔ)言實(shí)現(xiàn)輕量級(jí)OpenLdap弱密碼檢測(cè)工具,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以嘗試一下
    2022-09-09
  • Go Grpc Gateway兼容HTTP協(xié)議文檔自動(dòng)生成網(wǎng)關(guān)

    Go 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-06
  • Go語(yǔ)言構(gòu)建流數(shù)據(jù)pipeline的示例詳解

    Go語(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-02
  • golang http使用踩過(guò)的坑與填坑指南

    golang http使用踩過(guò)的坑與填坑指南

    這篇文章主要介紹了golang http使用踩過(guò)的坑與填坑指南,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2021-04-04
  • 一文介紹Go語(yǔ)言中的指針

    一文介紹Go語(yǔ)言中的指針

    指針是一個(gè)存儲(chǔ)變量?jī)?nèi)存地址的變量,本文主要介紹了Go語(yǔ)言中的指針,具有一定的參考價(jià)值,感興趣的可以了解一下
    2023-10-10
  • golang移除切片索引位置的元素的兩種方法

    golang移除切片索引位置的元素的兩種方法

    本文主要介紹了golang移除切片索引位置的元素的兩種方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2024-08-08
  • Go泛型之泛型約束示例詳解

    Go泛型之泛型約束示例詳解

    這篇文章主要給大家介紹了關(guān)于Go泛型之泛型約束的相關(guān)資料,泛型是靜態(tài)語(yǔ)言中的一種編程方式,這種編程方式可以讓算法不再依賴(lài)于某個(gè)具體的數(shù)據(jù)類(lèi)型,而是通過(guò)將數(shù)據(jù)類(lèi)型進(jìn)行參數(shù)化,以達(dá)到算法可復(fù)用的目的,需要的朋友可以參考下
    2023-12-12
  • golang中結(jié)構(gòu)體嵌套接口的實(shí)現(xiàn)

    golang中結(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

最新評(píng)論