Go 語言 基于類型斷言區(qū)別錯誤類型

2023-03-14 16:55 更新

原文鏈接:https://gopl-zh.github.io/ch7/ch7-11.html


7.11. 基于類型斷言區(qū)別錯誤類型

思考在os包中文件操作返回的錯誤集合。I/O可以因為任何數(shù)量的原因失敗,但是有三種經(jīng)常的錯誤必須進行不同的處理:文件已經(jīng)存在(對于創(chuàng)建操作),找不到文件(對于讀取操作),和權限拒絕。os包中提供了三個幫助函數(shù)來對給定的錯誤值表示的失敗進行分類:

package os

func IsExist(err error) bool
func IsNotExist(err error) bool
func IsPermission(err error) bool

對這些判斷的一個缺乏經(jīng)驗的實現(xiàn)可能會去檢查錯誤消息是否包含了特定的子字符串,

func IsNotExist(err error) bool {
    // NOTE: not robust!
    return strings.Contains(err.Error(), "file does not exist")
}

但是處理I/O錯誤的邏輯可能一個和另一個平臺非常的不同,所以這種方案并不健壯,并且對相同的失敗可能會報出各種不同的錯誤消息。在測試的過程中,通過檢查錯誤消息的子字符串來保證特定的函數(shù)以期望的方式失敗是非常有用的,但對于線上的代碼是不夠的。

一個更可靠的方式是使用一個專門的類型來描述結(jié)構化的錯誤。os包中定義了一個PathError類型來描述在文件路徑操作中涉及到的失敗,像Open或者Delete操作;并且定義了一個叫LinkError的變體來描述涉及到兩個文件路徑的操作,像Symlink和Rename。這下面是os.PathError:

package os

// PathError records an error and the operation and file path that caused it.
type PathError struct {
    Op   string
    Path string
    Err  error
}

func (e *PathError) Error() string {
    return e.Op + " " + e.Path + ": " + e.Err.Error()
}

大多數(shù)調(diào)用方都不知道PathError并且通過調(diào)用錯誤本身的Error方法來統(tǒng)一處理所有的錯誤。盡管PathError的Error方法簡單地把這些字段連接起來生成錯誤消息,PathError的結(jié)構保護了內(nèi)部的錯誤組件。調(diào)用方需要使用類型斷言來檢測錯誤的具體類型以便將一種失敗和另一種區(qū)分開;具體的類型可以比字符串提供更多的細節(jié)。

_, err := os.Open("/no/such/file")
fmt.Println(err) // "open /no/such/file: No such file or directory"
fmt.Printf("%#v\n", err)
// Output:
// &os.PathError{Op:"open", Path:"/no/such/file", Err:0x2}

這就是三個幫助函數(shù)是怎么工作的。例如下面展示的IsNotExist,它會報出是否一個錯誤和syscall.ENOENT(§7.8)或者和有名的錯誤os.ErrNotExist相等(可以在§5.4.2中找到io.EOF);或者是一個*PathError,它內(nèi)部的錯誤是syscall.ENOENT和os.ErrNotExist其中之一。

import (
    "errors"
    "syscall"
)

var ErrNotExist = errors.New("file does not exist")

// IsNotExist returns a boolean indicating whether the error is known to
// report that a file or directory does not exist. It is satisfied by
// ErrNotExist as well as some syscall errors.
func IsNotExist(err error) bool {
    if pe, ok := err.(*PathError); ok {
        err = pe.Err
    }
    return err == syscall.ENOENT || err == ErrNotExist
}

下面這里是它的實際使用:

_, err := os.Open("/no/such/file")
fmt.Println(os.IsNotExist(err)) // "true"

如果錯誤消息結(jié)合成一個更大的字符串,當然PathError的結(jié)構就不再為人所知,例如通過一個對fmt.Errorf函數(shù)的調(diào)用。區(qū)別錯誤通常必須在失敗操作后,錯誤傳回調(diào)用者前進行。



以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號