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
