golang教程之錯誤處理
文章目錄
錯誤處理
什麼是錯誤?
錯誤表示程式中的異常情況。 假設我們正在嘗試開啟檔案,檔案系統中不存在該檔案。 這是一種異常情況,它表示為錯誤。
Go中的錯誤是普通的舊值。 使用內建錯誤型別表示錯誤。
就像任何其他內建型別,如int,float64,…錯誤值可以儲存在變數中,從函式返回等等。
例子
讓我們立即開始嘗試開啟一個不存在的檔案的示例程式。
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")
}
在上面的程式第9行中,我們試圖在路徑/test.txt
Open
函式具有以下簽名,
func Open(name string) (file *File, err error)
如果檔案已成功開啟,則Open
函式將返回檔案處理程式,錯誤將為nil。 如果開啟檔案時出錯,將返回非零錯誤。
如果函式或方法返回錯誤,那麼按照慣例,它必須是函式返回的最後一個值。 因此,Open
函式返回err作為最後一個值。
在Go中處理錯誤的慣用方法是將返回的錯誤與nil進行比較。 nil值表示沒有發生錯誤,非nil值表示存在錯誤。 在我們的例子中,我們檢查錯誤是否存在,我們只需列印錯誤並從main函式返回。
執行此程式將列印
open /test.txt: No such file or directory
完美。我們收到一條錯誤訊息,指出該檔案不存在。
錯誤型別表示
讓我們深入一點,看看如何定義內建錯誤型別。 error是具有以下定義的介面型別,
type error interface {
Error() string
}
它包含一個帶有簽名Error() string
的方法。 實現此介面的任何型別都可以用作錯誤。 此方法提供錯誤的描述。
列印錯誤時,fmt.Println
函式在內部呼叫Error() string
方法以獲取錯誤的描述。 這是錯誤描述在第11行號中列印的方式。
從錯誤中提取更多資訊的不同方法
現在我們知道錯誤是一種介面型別,讓我們看看如何提取有關錯誤的更多資訊。
在上面我們看到的例子中,我們剛剛列印了錯誤的描述。 如果我們想要導致錯誤的檔案的實際路徑,該怎麼辦? 一種可能的方法是解析錯誤字串。 這是我們程式的輸出,
open /test.txt: No such file or directory
我們可以解析此錯誤訊息並獲取導致錯誤的檔案的檔案路徑“/test.txt”,但這是一種糟糕的方式。 錯誤描述可以隨時在較新版本的語言中更改,我們的程式碼將會中斷。
有沒有辦法可靠地獲取檔名? 答案是肯定的,它可以完成,標準的Go庫使用不同的方式來提供有關錯誤的更多資訊。 讓我們逐一看看它們。
1.斷言底層結構型別並從結構域中獲取更多資訊
如果仔細閱讀Open
函式的文件,可以看到它返回型別為* PathError
的錯誤。 PathError
是一種結構型別,它在標準庫中的實現如下,
type PathError struct {
Op string
Path string
Err error
}
func (e *PathError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.Error() }
如果您有興趣知道上述原始碼的存在位置,可以在此處找到https://golang.org/src/os/error.go?s=653:716#L11
從上面的程式碼中,您可以理解* PathError
通過宣告Error() string
方法來實現 error interface
。 此方法連線操作,路徑和實際錯誤並返回它。 因此我們收到了錯誤訊息,
open /test.txt: No such file or directory
PathError
結構的Path
欄位包含導致錯誤的檔案的路徑。 讓我們修改上面編寫的程式並列印路徑。
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")
}
在上面的程式中,我們在第10行中使用型別斷言,獲取錯誤介面的基礎值。 然後我們使用第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
結構有兩個方法Timeout()bool
和Temporary()bool
,它們返回一個布林值,指示錯誤是由於超時還是臨時錯誤。
讓我們編寫一個斷言* DNSError
型別的程式,並呼叫這些方法來確定錯誤是暫時的還是由於超時。
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)
}
注意:DNS查詢在playground上不起作用。 請在本地計算機上執行此程式。
在上面的程式中,我們試圖在第9行中獲取無效域名golangbot123.com的IP地址。 我們通過斷言它來鍵入* net.DNSError
來獲取錯誤的基礎值。 然後我們檢查錯誤是由於超時還是臨時的行號。
在我們的例子中,錯誤既不是暫時的也不是由於超時,因此程式將列印,
generic error: lookup golangbot123.com: no such host
如果錯誤是臨時的或由於超時,則相應的if
語句將被執行,我們可以適當地處理它。
3.直接比較
獲取有關錯誤的更多詳細資訊的第三種方法是直接與型別錯誤的變數進行比較。 讓我們通過一個例子來理解這一點。
filepath
包的Glob
函式用於返回與模式匹配的所有檔案的名稱。 模式格式錯誤時,此函式返回錯誤ErrBadPattern
。
ErrBadPattern
在filepath
包中定義如下。
var ErrBadPattern = errors.New("syntax error in pattern")
errors.New()
用於建立新錯誤。 我們將在下一個教程中詳細討論這個問題。
當模式格式錯誤時,Glob
函式返回ErrBadPattern
。
讓我們編寫一個小程式來檢查這個錯誤。
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)
}
在上面的程式中,我們搜尋模式的檔案[
這是一個格式錯誤的模式。 我們檢查錯誤是否為零。 要獲得有關錯誤的更多資訊,我們直接將它與filepath.ErrBadPattern
進行比較。如果滿足條件,則錯誤是由於格式錯誤造成的。 這個程式將列印,
syntax error in pattern
標準庫使用上述任何方法提供有關錯誤的更多資訊。 我們將在下一個教程中使用這些方法來建立自己的自定義錯誤。