Go 錯誤處理篇(一):error 類型及其使用
一、Go 語言錯誤處理機制
Go 語言錯誤處理機制非常簡單明了,不需要學(xué)習(xí)了解復(fù)雜的概念、函數(shù)和類型,Go 語言為錯誤處理定義了一個標準模式,即 error 接口,該接口的定義非常簡單:
type error interface {
Error() string
}
其中只聲明了一個 Error() 方法,用于返回字符串類型的錯誤消息。對于大多數(shù)函數(shù)或類方法,如果要返回錯誤,基本都可以定義成如下模式 —— 將錯誤類型作為第二個參數(shù)返回:
func Foo(param int) (n int, err error) {
// ...
}
然后在調(diào)用返回錯誤信息的函數(shù)/方法時,按照如下「衛(wèi)述語句」模板編寫處理代碼即可:
n, err := Foo(0)
if err != nil {
// 錯誤處理
} else {
// 使用返回值 n
}
非常簡潔優(yōu)雅。
二、返回錯誤實例并打印
關(guān)于自定義并返回 error 類型錯誤信息的使用示例,前面介紹函數(shù)多返回值時已經(jīng)演示過,我們可以通過 Go 標準錯誤包 errors 提供的 New() 方法快速創(chuàng)建一個 error 類型的錯誤實例:
func add(a, b int) (c int, err error) {
if (a < 0 || b < 0) {
err = errors.New("只支持非負整數(shù)相加")
return
}
a *= 2
b *= 3
c = a + b
return
}
我們參照上面介紹的 Go 錯誤處理標準模式,調(diào)用這個函數(shù)并編寫錯誤處理的代碼如下:
func main() {
if len(os.Args) != 3 {
fmt.Printf("Usage: %s num1 num2\n", filepath.Base(os.Args[0]))
return
}
x, _ := strconv.Atoi(os.Args[1])
y, _ := strconv.Atoi(os.Args[2])
// 通過多返回值捕獲函數(shù)調(diào)用過程中可能的錯誤信息
z, err := add(x, y)
// 通過「衛(wèi)述語句」處理后續(xù)業(yè)務(wù)邏輯
if err != nil {
fmt.Println(err)
} else {
fmt.Printf("add(%d, %d) = %d\n", x, y, z)
}
}
為了方便測試,我們將通過命令行參數(shù)傳遞 add 函數(shù)的參數(shù),這里我們引入了 os 包讀取命令行參數(shù),并通過 strconv 包提供的 Atoi 方法將其轉(zhuǎn)化為整型(命令行讀取參數(shù)值默認是字符串類型,轉(zhuǎn)化時忽略錯誤以便簡化處理流程),然后分別賦值為 x、y 變量,再調(diào)用 add 函數(shù)進行運算。
注意到我們在打印錯誤信息時,直接傳入了 err 對象實例,因為 Go 底層會自動調(diào)用 err 實例上的 Error() 方法返回錯誤信息并將其打印出來,就像普通類的 String() 方法一樣。
我們簡單測試下不傳遞參數(shù)、傳遞錯誤類型參數(shù)和傳遞正常參數(shù)這幾種場景,打印結(jié)果如下:

以上這種錯誤處理已經(jīng)能夠滿足我們?nèi)粘>帉?Go 代碼時大部分錯誤處理的需求了,事實上,Go 底層很多包進行錯誤處理時就是這樣做的。此外,我們還可以通過 fmt.Errorf() 格式化方法返回 error 類型錯誤,其底層調(diào)用的其實也是 errors.New 方法:
func Errorf(format string, a ...interface{}) error {
return errors.New(Sprintf(format, a...))
}
三、更復(fù)雜的錯誤類型
系統(tǒng)內(nèi)置錯誤類型
除了上面這種最基本的、使用 errors.New() 方法返回包含錯誤信息的錯誤實例之外,Go 語言內(nèi)置的很多包還封裝了更復(fù)雜的錯誤類型。
以 os 包為例,這個包主要負責(zé)與操作系統(tǒng)打交道,所以提供了 LinkError、PathError、SyscallError 這些實現(xiàn)了 error 接口的錯誤類型,以 PathError 為例,顧名思義,它主要用于表示路徑相關(guān)的錯誤信息,比如文件不存在,其底層類型結(jié)構(gòu)信息如下:
type PathError struct {
Op string
Path string
Err error
}
該錯誤類型除了組合 error 接口實現(xiàn) Error() 方法外,還提供了額外的操作類型字段 Op 和文件路徑字段 Path 以豐富錯誤信息,方便定位問題,該類型的 Error() 方法實現(xiàn)如下:
func (e *PathError) Error() string {
return e.Op + " " + e.Path + ": " + e.Err.Error()
}
我們可以在調(diào)用 os 包方法出錯時通過 switch 分支語句判定具體的錯誤類型,然后進行相應(yīng)的處理:
// 獲取指定路徑文件信息,對應(yīng)類型是 FileInfo
// 如果文件不存在,則返回 PathError 類型錯誤
fi, err := os.Stat("test.txt")
if err != nil {
switch err.(type) {
case *os.PathError:
// do something
case *os.LinkError:
// dome something
case *os.SyscallError:
// dome something
case *exec.Error:
// dome something
}
} else {
// ...
}
關(guān)于
os包的更多使用細節(jié),我們將在后面 Go 系統(tǒng)編程中詳細介紹。
自定義錯誤類型
當然,我們也可以仿照 PathError 的實現(xiàn)自定義一些復(fù)雜的錯誤類型,只需要組合 error 接口并實現(xiàn) Error() 方法即可,然后按照自己的需要為自定義類型添加一些屬性字段,這很簡單,就不展開介紹了。
四、小結(jié)
可以看到,Go 語言的錯誤和其他語言的錯誤和異常不同,它們就是從函數(shù)或者方法中返回的、和其他返回值并沒有什么區(qū)別的普通 Go 對象而已,如果程序出錯,要如何處理程序下一步的動作,是退出程序還是警告后繼續(xù)執(zhí)行,決定權(quán)完全在開發(fā)者手上。
(本文完)
學(xué)習(xí)過程中有任何問題,可以通過下面的評論功能或加入「Go 語言研習(xí)社」與學(xué)院君討論:
本系列教程首發(fā)在 geekr.dev,你可以點擊頁面左下角閱讀原文鏈接查看最新更新的教程。

