重學(xué)Go語言之錯誤處理與異常機(jī)制詳解
作為開發(fā)者來說,我們沒辦法保證程序在運(yùn)行過程中永遠(yuǎn)不會出現(xiàn)異常,對于異常,在很多編程語言中,可以用 try-catch
語句來捕獲,而Go語言的開發(fā)者顯然覺得 try-catch
被濫用了,因此 Go
不支持使用 try-catch
語句捕獲異常處理。
那么,Go語言是如何定義和處理程序的異常呢?
Go語言的將程序運(yùn)行出現(xiàn)的問題分為兩種:錯誤(error
)和異常(exception
),錯誤是程序(比如一個函數(shù))運(yùn)行的預(yù)期結(jié)果之一,當(dāng)你調(diào)用一個函數(shù)時,就應(yīng)該處理出現(xiàn)錯誤的情況,而異常是不可預(yù)期的(當(dāng)然也可以手動觸發(fā))且會導(dǎo)致程序中斷運(yùn)行的嚴(yán)重錯誤。
Go錯誤處理策略
編寫Go語言程序,一般推薦通過函數(shù)的最后一個返回值告訴調(diào)用者函數(shù)是否執(zhí)行成功,可以分兩種情況來討論:
第一種情況,如果函數(shù)的執(zhí)行結(jié)果只有正確與失敗,那么返回值可以是 boolean
類型:
func exists(key string) bool { //to check if the key is exists return true } if ok := exists("test"); ok { // do something }
第二種情況,如果要讓調(diào)用者得到更詳細(xì)的錯誤信息,顯然只返回一個布爾值是不夠的,這時候可以返回一個 error
類型,error
類型是一個接口,只有一個 Error
方法的接口:
type error interface { Error() string }
通過 error
類型的 Error
方法,可以獲得更詳細(xì)的錯誤信息,方便調(diào)用者做進(jìn)一步的處理,因此在 Go
標(biāo)準(zhǔn)庫的方法和函數(shù)中,一般都會將 error
類型作為最后一個返回值,比如我們以前文章中講到的 os
包下的 Open
和 Create
函數(shù):
package os func Open(name string) (*File, error) { return OpenFile(name, O_RDONLY, 0) } func Create(name string) (*File, error) { return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666) }
當(dāng)函數(shù)最后一個返回值 error
不為 nil
時,表示執(zhí)行不成功,此時其他的返回值就應(yīng)該忽略:
file,err := os.Open("./my.dat") if err != nil{ return err } defer file.Close()
上面代碼中,如果 error
不為 nil
,那么變量 file
則為 nil
,表示無法獲得一個文件句柄,這時候直接返回錯誤,因?yàn)槿绻倮^續(xù)往下執(zhí)行,可能會引發(fā)程序崩潰。
當(dāng)然并不是所有情況下一遇到返回的 error
不為 nil
,就要拋棄其他返回值,比如使用 Read()
方法讀取文件時:
Read(p []byte) (n int, err error)
在讀取文件到結(jié)尾時 Read
方法會返回一個 io.EOF
的錯誤:
var EOF = errors.New("EOF")
此時雖然 error
不為 nil
,但仍然應(yīng)該把讀取到的字節(jié)數(shù)組保存,而不是拋棄掉。
創(chuàng)建error的幾種方式
當(dāng)我們開發(fā)自己的函數(shù)時,也可以創(chuàng)建自己的錯誤類型,有以下幾種方式:
errors包
errors
包下的 New()
函數(shù)可以創(chuàng)建一個只有文本信息的 error
類型:
func getFile(name string)(*os.file,error){ if name == ""{ return nil,errors.New("file name could not be empty") } }
fmt包
fmt
包下的 Errorf
函數(shù)可以將文本格式后作為error類型的錯誤信息,并返回一個error類型,因此其作用與errors.New函數(shù)類似
func getFile(name string)(*os.file,error){ if name == ""{ return nil,fmt.Errorf("file name could not be empty") } }
fmt.Errorf
函數(shù)還能封裝其他 error
類型,再返回一個新的 error
類型,以此形成一條完整的錯誤鏈條:
doc, err := html.Parse(resp.Body) resp.Body.Close() if err != nil { return nil, fmt.Errorf("parsing %s as HTML: %v", url,err) }
自定義錯誤類型
其實(shí),上面兩種創(chuàng)建錯誤類型的方式,本質(zhì)上都是實(shí)現(xiàn) error
接口,我們也可以創(chuàng)建一個擁有 Error
方法的類型來實(shí)現(xiàn) error
接口:
type Result struct { Code int Message string Data interface{} } func (r Result) Error() string { return r.Message }
如何處理錯誤
當(dāng)調(diào)用的函數(shù)或方法的返回值有 error
類型時,最簡單的當(dāng)然可以選擇直接忽略錯誤,不過更恰當(dāng)?shù)姆绞绞翘幚韺?yīng)的錯誤,有以下幾種處理策略:
直接返回錯誤
對于函數(shù)來說,如果在執(zhí)行時遇到錯誤,可以直接返回給上層調(diào)用者:
func SendMessage(url string) error { if url == ""{ return errors.New("url can't not be empty") } _, err := http.Get(url) if err != nil { return err } return nil }
記錄日志并繼續(xù)運(yùn)行
當(dāng)調(diào)用函數(shù)時遇到返回的錯誤,如果不影響程序運(yùn)行,也可以選擇記錄錯誤日志并往下執(zhí)行:
if err := SendMessage("https://xxx/sendMessage");err != nil{ log.Printf("the message sending been broken by : %v\n", err) }
記錄日志并結(jié)束運(yùn)行
如果錯誤影響程序的執(zhí)行,也可以記錄日志后,退出程序執(zhí)行:
if err := SendMessage("https://xxx/sendMessage");err != nil{ log.Printf("the message sending been broken by : %v\n", err) os.Exit(1) }
記錄日志并退出執(zhí)行,直接用 log
包的 Fatalf
函數(shù)就可以做到,因此上面代碼的更簡潔做法是:
if err := SendMessage("https://xxx/sendMessage");err != nil{ log.Fatalf("the message sending been broken by : %v\n", err) }
Go異常處理機(jī)制
在Go語言中,異常是指會引發(fā)程序崩潰無法繼續(xù)運(yùn)行的錯誤,比如數(shù)組越界或者空指針引用等情況,這時候會觸發(fā) panic
異常:
package main func main() { var s = []int{1, 2, 3} s[3] = 10 }
上面程序運(yùn)行結(jié)果如下,可以看出觸發(fā)了數(shù)組越界的異常:
panic: runtime error: index out of range [3] with length 3
無論是在主協(xié)程還是子協(xié)程中,一旦觸發(fā) panic
異常,整個程序都會終止運(yùn)行。
panic函數(shù)
除了數(shù)組越界等不可預(yù)測的異常會自動觸發(fā) panic
,也可以手動調(diào)用 panic
函數(shù)觸發(fā)異常來終止程序的運(yùn)行:
func loadConfig(path string){ panic("can't load the config file on path " + path) }
一個良好的程序最好不要主動調(diào)用 panic
函數(shù),尤其是開發(fā)類庫的時候,最好通過返回 error
類型來告訴調(diào)用者發(fā)生了什么錯誤,而不是觸發(fā) panic
導(dǎo)致程序終止運(yùn)行。
recover函數(shù)
當(dāng)發(fā)生 panic
異常時,如果不捕獲得異常,那么程序就是終止運(yùn)行,在Go語言中,可以用 defer
語句和 recover
函數(shù)的模式來捕獲 panic
異常:
package main import ( "fmt" "log" ) func main() { n1 := FindElementByIndex(1) fmt.Println(n1) n2 := FindElementByIndex(4) fmt.Println(n2) } func FindElementByIndex(index int) int { defer func() { if e := recover(); e != nil { log.Fatal(e) } }() s := []int{1, 2, 3, 4} return s[index] }
小結(jié)
調(diào)用函數(shù)時,錯誤是執(zhí)行的結(jié)果,應(yīng)該及時處理,而異常往往不可控,可以用defer+recover的模式進(jìn)行捕獲。
到此這篇關(guān)于重學(xué)Go語言之錯誤處理與異常機(jī)制詳解的文章就介紹到這了,更多相關(guān)Go錯誤處理與異常機(jī)制內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用golang實(shí)現(xiàn)一個MapReduce的示例代碼
這篇文章主要給大家介紹了關(guān)于如何使用golang實(shí)現(xiàn)一個MapReduce,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-09-09Go語言中的數(shù)據(jù)格式(json、xml?、msgpack、protobuf)使用總結(jié)
在分布式的系統(tǒng)中,因?yàn)樯婕暗綌?shù)據(jù)的傳輸,所以一定會進(jìn)行數(shù)據(jù)的交換,此時就要定義數(shù)據(jù)交換的格式,例如二進(jìn)制、Json、Xml等等。本文總結(jié)了Go語言中的數(shù)據(jù)格式,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-07-07GO語言字符串處理Strings包的函數(shù)使用示例講解
這篇文章主要為大家介紹了GO語言字符串處理Strings包的函數(shù)使用示例講解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04Go?slice切片make生成append追加copy復(fù)制示例
這篇文章主要為大家介紹了Go使用make生成切片、使用append追加切片元素、使用copy復(fù)制切片使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06Go?channel結(jié)構(gòu)體源碼和讀寫和關(guān)閉過程詳解
這篇文章主要介紹了Go?channel結(jié)構(gòu)體源碼和讀寫和關(guān)閉過程,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-05-05使用go實(shí)現(xiàn)一個超級mini的消息隊(duì)列的示例代碼
本文主要介紹了使用go實(shí)現(xiàn)一個超級mini的消息隊(duì)列的示例代碼,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-12-12使用go實(shí)現(xiàn)簡易比特幣區(qū)塊鏈公鏈功能
這篇文章主要介紹了使用go實(shí)現(xiàn)簡易比特幣區(qū)塊鏈公鏈功能,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01SingleFlight模式的Go并發(fā)編程學(xué)習(xí)
這篇文章主要為大家介紹了SingleFlight模式的Go并發(fā)編程學(xué)習(xí),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-04-04