go errors 包講解



title: go errors 包講解
author: "edte"
tags: ["go"]
date: 2020-06-02

引言

go 包實現了處理 error 的一些功能。

這是源碼注釋

// Package errors implements functions to manipulate errors.

errors 包的源碼放在 $GOROOT/src/errors

查看安裝目錄

go env GOROOT

為了方便閱讀源碼和調試,建議將源碼復制后作為新項目打開

cp -r errors ~/

文件

errors 包有兩個文件,errors.go 和 wrap.go 以及三個測試文件。

errors.go

errors.go 中有一個 errorString 結構體

// errorString is a trivial implementation of error.
type errorString struct {
	s string
}

讀注釋我們明白,這個結構體是 error 的簡單實現。我們可以自定義錯誤的類型,而這個結構體就是源碼中對錯誤的一個簡單實現。

然后有這個結構體的 constructor,即 New 函數

// New returns an error that formats as the given text.
// Each call to New returns a distinct error value even if the text is identical.
func New(text string) error {
	return &errorString{text}
}

我們發現返回一個 error, 繼續讀源碼,我們知道這是一個接口,在 builtin 包 builtin.go 文件中, builtin 包用來給有預定義標識符生成文檔,也就是說這個包里都是一些預定義標識符,這些標識符的具體實現沒有在這個包里,寫這個包只是為了使用 goget 生成文檔。

// The error built-in interface type is the conventional interface for
// representing an error condition, with the nil value representing no error.
type error interface {
	Error() string
}

這個接口是用來表示錯誤狀態的,只要實現了 Error() 方法,那么就實現了這個接口。也就是說,我們可以自定義一個類型,然后實現 Error() 方法,這個類型就是我們自定義的錯誤類型。當這個接口為 nil 時,沒有錯誤,所以我們一般來判斷這個接口是否為空,來判斷是否出錯。

func main() {
	if err := A(); err != nil {
	//
	}
}

余下的 Error() 函數就是 errorString 的方法,所以 errorString 實現了 error 接口,所以 errorStirng 可以轉換為 error 類型,所以才返回 errorString 類型,而函數簽名處是 error 類型。

我們可以看 fmt 包 errors.go 文件中,就自定義了一個錯誤類型

type wrapError struct {
	msg string
	err error
}

func (e *wrapError) Error() string {
	return e.msg
}

從上面的代碼我們可以知道,errors.go 文件主要就是 圍繞 error 接口展開的,接下來我們來看測試文件中具體的例子。

example_test.go

自定義一個類型

// MyError is an error implementation that includes a time and message.
type MyError struct {
	When time.Time
	What string
}

然后實現 error 接口

func (e MyError) Error() string {
	return fmt.Sprintf("%v: %v", e.When, e.What)
}

這里使用了 opps 函數封裝了一下,我們可以模仿 errors.go 中把它改為 constructor,即 New 函數。

func New(s string) error {
	return &MyError{time.Date(1989, 3, 15, 22, 30, 0, 0, time.UTC), s}
}
func main() {
	if err := New("haha"); err != nil {
		fmt.Println(err)
	}
}

可得 1989-03-15 22:30:00 +0000 UTC: haha,這個即我們定義的 error 類型的具體實現。

errors_test.go

  • ExampleNew()

有測試 New 函數的,即新生成一個 error 類型,這個類型的格式只有一個 string.

func ExampleNew() {
	err := errors.New("emit macho dwarf: elf header corrupted")
	if err != nil {
		fmt.Print(err)
	}
	// Output: emit macho dwarf: elf header corrupted
}
  • TestErrorMethod()

有測試方法的,我們生成一個 error 類型后,這個類型實現了 error() 方法

func TestErrorMethod(t *testing.T) {
	err := errors.New("abc")
	if err.Error() != "abc" {
		t.Errorf(`New("abc").Error() = %q, want %q`, err.Error(), "abc")
	}
}

這里容易搞混 err 和 err.Error(), 不知道它們有什么區別,這里我們來看一下

func main() {
	err := errors.New("sdf")

	fmt.Println(reflect.TypeOf(err)) //*errors.errorString
	fmt.Println(reflect.ValueOf(err)) //sdf

	e := err.Error()
	fmt.Println(reflect.TypeOf(e))  //string
	fmt.Println(reflect.ValueOf(e)) //sdf
}

New 函數返回一個 error 接口的實例,這個接口是 errorString 的實現,所以 err 是個接口,它的動態類型是 *errors.errorString, 值是實例傳入的 errorString 的值 sdf.

func New(text string) error {
	return &errorString{text}
}

err 是個接口,error() 就是 err 接口方法集中的 error() 方法,所以這里 err.Error() 就是調用 Error() 方法,把返回值賦給 e,返回值是 string, 所以 e 的類型是 string

type error interface {
	Error() string
}

而 e 是實例 err 創建出來的,err 接口這里是 errorString 實現的,所以這里就是傳入的 sdf,sdf 賦給 errorString 的 是,Error() 再獲取 s, 所以 e 的 值就是 sdf

  • ExampleNew_errorf()

有 格式化新建 error 的,即 ExampleNew_errorf 函數,這個函數用了 fmt 包中的 Errorf 函數,這個函數用來用來新建格式化 error,用到了自建的一種格式,即 wrapError struct, 也就是我們在 error 自建類型中講到的,不過這里不具體展開其實現原理。

// The fmt package's Errorf function lets us use the package's formatting
// features to create descriptive error messages.
func ExampleNew_errorf() {
	const name, id = "bimmler", 17
	err := fmt.Errorf("user %q (id %d) not found", name, id)
	if err != nil {
		fmt.Print(err)
	}
	// Output: user "bimmler" (id 17) not found
}
  • TestNewEqual()

有關於 New 函數特點的,我們再來看一下 New 函數的源碼

func New(text string) error {
	return &errorString{text}
}

它返回時,建立了一個臨時變量 errorString 類型,而我們在區分 err 和 error 時知道,New 函數返回時,返回了一個指針,如果忘記了,可以運行

func main() {
	fmt.Println(reflect.TypeOf(errors.New("sdf")))
}

所以如果直接比較兩個 errors.New(), 哪怕輸入的字符串相同,它們也是不相同的兩個變量

func main() {
	fmt.Println(errors.New("f") == errors.New("f"))
}

所以這里是 flase, 我們就不能這樣判斷錯誤是否相同,我們可以先賦值給一個變量

func main() {
	err := errors.New("df")
	fmt.Println(err == err)
}

這里 err 是一個變量,所以是 true, 或者我們可以通過 Error() 方法獲取它們的值,再比較

func main() {
	fmt.Println(errors.New("f").Error() == errors.New("f").Error())
}

故是 true

wrap.go

wrap_test.go


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM