go語言異常處理
error接口
go語言引入了一個關於錯誤錯里的標准模式,即error接口,該接口的定義如下:
type error interface{
Error() string
}
對於要返回錯誤的大多數函數來說,大致上都可以定義為如下的模式,將error作為多個返回值中的最后一個,但是這並非是強制要求
func Foo(param int) (n int,err error){
//
}
調用該函數的時候建議按照如下的方式處理錯誤情況
n,err := Foo(0)
if err != nil{
//處理錯誤
}else{
//處理結果
}
那么,如何自定義error類型呢,我們以Go庫中的實際代碼為例來說明,首先,定義一個承載錯誤信息的類型,因為Go中的接口非常的靈活,你根本不需要像別的語言一樣使用繼承或則implements來明確指定類型和接口之間的關系,代碼如下:
type PathError struct{
Op string
Path string
Err error
}
這樣定義后,編譯器如何知道PathError可以當成一個error來傳遞呢?關鍵在於下面的代碼實現了Error() 方法:
func (e *PathError) Error() string{
return e.Op + " " + e.Path +" " + e.Err.Error()
}
定義了上述方法之后,就可以直接返回PathError變量了,如下:
func test(name string) (fi FIleInfo,err error){
var stat syscall.Stat_t
err = syscall.Stat(name,&stat)
if err != nil{
return nil,$PathError{"stat",name,err}
}
return file,nil
}
如果在處理錯誤的時候要獲取錯誤的詳細信息,就需要用到類型的轉換知識了:
fi,err := os.Stat("a.txt")
if err != nil{
if e,ok := err.(*os.PathError);ok && e.Err != nil{
//獲取PathError類型變量e總的其他信息
}
}
defer
關鍵字defer是Go引入的一個很有意思的特性,他能保證在函數中發生異常的情況下,defer定義的語句仍然會被執行,如下:
func CopyFile(dst,src string) (w int64,err error){
srcFile,err := os.Open(src)
if err != nil{
return
}
defer srcFile.Close()
dstFile,err := os.Create(dst)
if err != nil{
return
}
defer dstFile.Close()
return io.Copy(dstFile,srcFile)
}
即使Copy函數拋出異常,Go仍然會保證文件被正常的關閉,如果據的一句話干不完清理工作的話,可以在defer后加一個匿名函數:
defer func(){
//
}()
值得注意的是一個函數中可以定義多個defer,並且defer語句的順序是按照先進后出的順序執行的,也就是說最后一個defer語句將最先被執行
panic()和recover()
GO引入了兩個內置的函數panic()和recover()以報告和處理運行時錯誤和程序中的錯誤場景
func panic(interface{})
func recover() interface{}
當在一個函數中調用panic函數后,正確的執行流程會被立即終止,但函數中之前使用defer關鍵字延遲執行的語句將正常展開執行,之后該函數將返回到調用函數,並導致逐步向上執行panic流程,直到所屬的goroutine中所有正在執行的函數被終止,錯誤信息將被報告,這個過程成為錯誤處理流程
從panic中傳入的interface{}中我們可以看出,該函數接收任意類型的數據
recover用於終止錯誤處理流程,一般情況下,recover應該在一個使用defer關鍵字的函數中執行以有效截取錯誤處理流程,如果在發生異常的goroutine中沒有調用recover,會導致該goroutine所屬的進程直接退出
下面是一個常用的場景
defer func(){
if r := recover();r!=nil{
log.Printf("Runtime error caught: %v",r)
}
}