[譯] Part 30: Golang 中的Error處理

咔嘰咔嘰發表於2019-03-31

什麼是Error?

Error表示程式中的異常情況。假設我們正在嘗試開啟檔案,檔案系統中不存在該檔案,那麼這是一種異常情況,它就代表一種error。 Go中使用內建的error型別表示錯誤。 就像任何其他的內建型別,如int,float64,... error可以儲存在變數中,從函式返回等等。

例子

用開啟了一個不存在的檔案的示例程式來解釋一下。

package main

import (  
    "fmt"
    "os"
)

func main() {  
    f, err := os.Open("/test.txt")
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println(f.Name(), "opened successfully")
}
複製程式碼

Run in playground

上面的程式中,我們試圖在路徑/test.txt中開啟檔案。 os包的Open函式定義如下,

func Open(name string) (file *File, err error)
複製程式碼

如果檔案被成功開啟,則Open函式將會返回開啟的檔案,同時err為nil。如果在開啟檔案時出錯,err將返回非nil的錯誤。

如果一個函式或者方法返回錯誤,那麼按照慣例,函式返回值的最後面那一個值就是err。所以,Open函式返回的最後一個值是err。

在Go中處理錯誤的慣用方法是將返回的錯誤與nil進行比較。 如果返回的err是nil,則表示沒有發生錯誤,非nil值則表示存在錯誤。在我們的例子中,我們在第10行檢查錯誤的返回值,如果它不是nil,我們只需列印錯誤並從main函式返回。

執行上述程式將列印,

open /test.txt: No such file or directory  
複製程式碼

完美?。我們列印了一條錯誤訊息,顯示了該檔案不存在。

Error type

讓我們再深入一點,看看如何定義內建的錯誤型別。 error是具有以下定義的介面型別,

type error interface {  
    Error() string
}
複製程式碼

它包含一個Error方法,實現此介面的Error就可以被用作error。此方法提供了error的描述

當列印error時,fmt.Println函式在內部呼叫Error方法以獲取error的描述。上述例子的第11行就展示了error描述的列印。

如何從error中提取更多的資訊

在上面的例子中我們看到列印了錯誤的描述。如果我們想要導致error的檔案的實際路徑,該怎麼辦?一種可能的方法是解析錯誤字串。這是輸出,

open /test.txt: No such file or directory 
複製程式碼

我們可以獲得錯誤內容並獲取導致錯誤的檔案的檔案路徑為“/test.txt”,但這不是一種很好的方式。在有些情況下,我們應該捕獲更多的錯誤資訊,然後根據不同的錯誤去做區別處理。(類似其他的語言catch多個錯誤)

有沒有辦法可靠地獲取檔名?答案是肯定的,標準的Go庫使用不同的方式來提供有關error的更多資訊。讓我們逐一看看它們。

1. 從底層結構型別的欄位中獲取更多資訊

如果仔細閱讀Open函式的文件,可以看到其error返回型別為*PathErrorPathError是一種結構型別,它在標準庫中的實現如下,

type PathError struct {  
    Op   string
    Path string
    Err  error
}

func (e *PathError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.Error() }  
複製程式碼

如果有興趣知道上述原始碼的位置,可以在此處找到golang.org/src/os/erro…

從上面的程式碼中,可以知道* PathError通過宣告Error方法來實現error的介面。此方法返回一個包含路徑,實際錯誤拼接的字串並返回。因此我們收到了這個錯誤內容,

open /test.txt: No such file or directory  
複製程式碼

PathError structPath欄位包含導致錯誤的檔案的路徑。讓我們修改上面編寫的程式並列印路徑。

package main

import (  
    "fmt"
    "os"
)

func main() {  
    f, err := os.Open("/test.txt")
    if err, ok := err.(*os.PathError); ok {
        fmt.Println("File at path", err.Path, "failed to open")
        return
    }
    fmt.Println(f.Name(), "opened successfully")
}
複製程式碼

Run in playgroud

在上面的程式中,我們在第10行從結構欄位中去獲取error的基礎值。然後我們在11行使用err.Path列印路徑。 輸出如下,

File at path /test.txt failed to open 
複製程式碼

我們已成功使用結構欄位從錯誤中獲取檔案路徑。

2. 使用基礎結構型別的方法獲取更多資訊

第二種方法是通過呼叫struct型別的方法獲取更多資訊。

讓我們通過一個例子更好地理解這一點。

標準庫中的DNSError結構型別定義如下,

type DNSError struct {  
    ...
}

func (e *DNSError) Error() string {  
    ...
}
func (e *DNSError) Timeout() bool {  
    ... 
}
func (e *DNSError) Temporary() bool {  
    ... 
}
複製程式碼

從上面的程式碼中可以看出,DNSError結構有兩個方法TimeoutTemporary,它們返回一個布林值,指示error是由於超時還是暫時的。

讓我們編寫一個包含* DNSError型別的程式,並呼叫這些方法來確定error是暫時錯誤型別還是超時錯誤型別。

package main

import (  
    "fmt"
    "net"
)

func main() {  
    addr, err := net.LookupHost("golangbot123.com")
    if err, ok := err.(*net.DNSError); ok {
        if err.Timeout() {
            fmt.Println("operation timed out")
        } else if err.Temporary() {
            fmt.Println("temporary error")
        } else {
            fmt.Println("generic error: ", err)
        }
        return
    }
    fmt.Println(addr)
}
複製程式碼

注意:LookupHost在playground上不起作用。請在本地計算機上執行此程式。

在上面的程式中,我們在第9行試圖獲取一個無效域名golangbot123.com的IP地址。我們通過* net.DNSError來獲取錯誤的基礎值。然後我們在第10和13行分別檢查error是由於超時還是臨時引起的。

在我們的例子中,錯誤既不是暫時的也不是由於超時,因此程式將列印,

generic error:  lookup golangbot123.com: no such host 
複製程式碼

如果錯誤是臨時的或由於超時,則執行相應的if語句,這樣就可以分情況適當地處理它了。

3. 直接比較

獲取有關錯誤的更多詳細資訊的第三種方法是直接與型別錯誤的變數進行比較。讓我們通過一個例子來理解這一點。

filepath包的Glob函式用於返回與正則模式匹配的所有檔案的名稱。格式錯誤時,此函式返回錯誤ErrBadPattern

ErrBadPatternfilepath包中定義如下,

var ErrBadPattern = errors.New("syntax error in pattern")
複製程式碼

errors.New用於建立一個新的error。我們將在下一個教程中詳細討論這個問題。

當匹配發生格式錯誤時,Glob函式返回ErrBadPattern

讓我們編寫一個小程式來檢查這個error

package main

import (  
    "fmt"
    "path/filepath"
)

func main() {  
    files, error := filepath.Glob("[")
    if error != nil && error == filepath.ErrBadPattern {
        fmt.Println(error)
        return
    }
    fmt.Println("matched files", files)
}
複製程式碼

Run in palygroud

在上面的程式中,我們匹配帶有[的檔案,這是一個格式錯誤的方式。我們檢查error是否為nil。要獲得有關error的更多資訊,我們在第10行直接將它與filepath.ErrBadPattern進行比較。如果條件滿足,則是因為格式錯誤。所以該程式將輸出,

syntax error in pattern 
複製程式碼

標準庫使用上述方法提供有關error的更多資訊。我們將在下一個教程中使用這些方法來建立自己的自定義錯誤。

不要忽視Error

永遠不要忽視error。忽略error會引發麻煩。讓我重寫一個示例,該示例忽略了error處理。

package main

import (  
    "fmt"
    "path/filepath"
)

func main() {  
    files, _ := filepath.Glob("[")
    fmt.Println("matched files", files)
}
複製程式碼

Run in playground

我們從前面的例子中已經知道匹配是無效的。我在第9行通過使用_識別符號忽略了Glob函式返回的error。然後在第10行列印該匹配的檔案。程式將列印,

matched files [] 
複製程式碼

由於我們忽略了error,似乎沒有輸出匹配檔案,但實際上是模式本身的格式不正確,這樣就不知道到底是什麼導致泐該 error的產生。所以不要忽視error

在本教程中,我們討論瞭如何處理程式中發生的error以及如何檢查error以從中獲取更多資訊。

在下一個教程中,我們將建立自己的自定義 error,併為標準error新增更多上下文。

相關文章