golang 語(yǔ)言中錯(cuò)誤處理機(jī)制
與其他主流語(yǔ)言如 Javascript、Java 和 Python 相比,Golang 的錯(cuò)誤處理方式可能和這些你熟悉的語(yǔ)言有所不同。所以才有了這個(gè)想法根大家聊一聊 golang 的錯(cuò)誤處理方式,以及實(shí)際開發(fā)中應(yīng)該如何對(duì)錯(cuò)誤進(jìn)行處理。因?yàn)榉窒砻鎸?duì) Golang有一個(gè)基本的了解 developers, 所以一些簡(jiǎn)單地方就不做贅述了。
如何定義錯(cuò)誤
在 golang 語(yǔ)言中,無(wú)論是在類型檢查還是編譯過(guò)程中,都是將錯(cuò)誤看做值來(lái)對(duì)待,和 string 或者 integer 這些類型值并不差別。聲明一個(gè) string 類型變量和聲明一個(gè) error 類型變量是沒(méi)什么區(qū)別的。
你可以定義接口作為 error 的類型,有關(guān) error 能夠提供什么樣信息都是由自己決定的,這是 error 在 golang 作為值的好處,不過(guò)這樣做也自然有其壞處,有關(guān) error 定義好壞就全由其定義開發(fā)人員所決定,也就是有關(guān) error 融入過(guò)多人為的主觀因素。
package main import ( "fmt" "io/ioutil" ) func main(){ dir, err := ioutil.TempDir("","temp") if err != nil{ fmt.Errorf("failed to create temp dir: %v",err) } }
錯(cuò)誤在語(yǔ)言中的重點(diǎn)地位
在 Go 語(yǔ)言中錯(cuò)誤處理設(shè)計(jì)一直大家喜歡討論的內(nèi)容,錯(cuò)誤處理是該語(yǔ)言的核心,但該語(yǔ)言并沒(méi)有規(guī)定如何處理錯(cuò)誤。社區(qū)已經(jīng)為改進(jìn)和規(guī)范錯(cuò)誤處理做出了努力,但許多人忽略了錯(cuò)誤在我們應(yīng)用程序領(lǐng)域中的核心地位。也就是說(shuō),錯(cuò)誤與客戶和訂單類型一樣重要。
Golang中的錯(cuò)誤
錯(cuò)誤表示在應(yīng)用程序中發(fā)生了不需要的情況。比方說(shuō),想創(chuàng)建一個(gè)臨時(shí)目錄,在那里可以為應(yīng)用程序存儲(chǔ)一些文件,但這個(gè)目錄的創(chuàng)建失敗了。這是一個(gè)不期望的情況,就可以用錯(cuò)誤來(lái)表示。
通過(guò)創(chuàng)建自定義錯(cuò)誤可以將更豐富錯(cuò)誤信息傳遞給調(diào)用者。個(gè)返回值返回將錯(cuò)誤交給調(diào)用函數(shù)人來(lái)處理錯(cuò)誤。Golang 本身允許函數(shù)具有多個(gè)返回值,所以通常把錯(cuò)誤作為函數(shù)最后一個(gè)參數(shù)返回給調(diào)用者來(lái)處理。
errors 是 I/O
- 有時(shí)候開發(fā)人員是 error 的生產(chǎn)者(寫 error)
- 有時(shí)候開發(fā)人員又是 error 的消費(fèi)者(讀 error)
也就是我們開發(fā)程序一部分工作是讀取和寫入 error
errors 的上下文
什么是 error 的上下文呢? 如何定義 error 需要考慮一些因素,例如在不同程序我們定義 error 和處理 error 方式也不僅相同
- CLI 工具
- 庫(kù)
- 長(zhǎng)時(shí)間運(yùn)行的系統(tǒng)
而且我們需要考慮使用程序的人群,他們是什么方式來(lái)使用系統(tǒng),這些因素都是我們?cè)O(shè)計(jì)也好定義錯(cuò)誤信息要考慮的因素。
錯(cuò)誤的類型
就錯(cuò)誤核心,那么錯(cuò)誤可能是我們預(yù)料之中的錯(cuò)誤,錯(cuò)誤也可能是我們沒(méi)有考慮到,例如無(wú)效內(nèi)存,數(shù)組越界,也就是單靠代碼自身暫時(shí)是解決不了的錯(cuò)誤 ,這樣的誤差往往讓代碼恐慌,所以 Panic。通常這樣錯(cuò)誤對(duì)于程序是災(zāi)難性的失敗,無(wú)法修復(fù)的。
自定義錯(cuò)誤
如前所述,錯(cuò)誤使用內(nèi)置的錯(cuò)誤接口類型來(lái)表示,其定義如下。
type error interface { Error() string }
下面舉了 2 例子來(lái)定義 error ,分別定義兩個(gè) struct 都實(shí)現(xiàn)了 Error() 接口即可
type SyntaxError struct { Line int Col int } func (e *SyntaxError) Error() string { return fmt.Sprintf("%d:%d: syntax error", e.Line, e.Col) }
type InternalError struct { Path string } func (e *InternalError) Error() string { return fmt.Sprintf("parse %v: internal error", e.Path) }
該接口包含一個(gè)方法 Error() ,以字符串形式返回錯(cuò)誤信息。每一個(gè)實(shí)現(xiàn)了錯(cuò)誤接口的類型都可以作為一個(gè)錯(cuò)誤使用。當(dāng)使用 fmt.Println 等方法打印錯(cuò)誤時(shí),Golang 會(huì)自動(dòng)調(diào)用 Error() 方法。
在 Golang 中,有多種創(chuàng)建自定義錯(cuò)誤信息的方法,每一種都有自己的優(yōu)點(diǎn)和缺點(diǎn)。
基于字符串的錯(cuò)誤
基于字符串的錯(cuò)誤可以用 Golang 中兩個(gè)開箱即用方法來(lái)自定義錯(cuò)誤,適用哪些僅返回描述錯(cuò)誤信息的相對(duì)來(lái)說(shuō)比較簡(jiǎn)單的錯(cuò)誤。
err := errors.New("math: divided by zero")
將錯(cuò)誤信息傳入到 errors.New() 方法可以用來(lái)新建一個(gè)錯(cuò)誤
err2 := fmt.Errorf("math: %g cannot be divided by zero", x)
fmt.Errorf 通過(guò)字符串格式方式,可以將錯(cuò)誤信息包含你錯(cuò)誤信息中。也就是為錯(cuò)誤信息添加了一些格式化的功能。
自定義數(shù)據(jù)結(jié)構(gòu)的錯(cuò)誤
可以通過(guò)在你的結(jié)構(gòu)上實(shí)現(xiàn) Error 接口中定義的 Error() 函數(shù)來(lái)創(chuàng)建自定義的錯(cuò)誤類型。下面是一個(gè)例子。
Defer, panic 和 recover
Go 并不像許多其他編程語(yǔ)言(包括 Java 和 Javascript )那樣有異常,但有一個(gè)類似的機(jī)制,即 "Defer, panic 和 recover"。然而,panic 和 recover 的使用情況與其他編程語(yǔ)言中的異常非常不同,因?yàn)榇a本身無(wú)法應(yīng)對(duì)時(shí)候和不可恢復(fù)的情況下使用。
Defer
有點(diǎn)類似析構(gòu)函數(shù),在函數(shù)執(zhí)行完畢后做一些資源釋放等收尾工作,好處其執(zhí)行和其在代碼中位置并沒(méi)有關(guān)系,所以可以將其寫在你讀寫資源語(yǔ)句后面,以免隨后忘記做一些資源釋放的工作。關(guān)于 defer 輸出也是面試時(shí),面試官喜歡問(wèn)的一個(gè)問(wèn)題。
package main import( "fmt" "os" ) func main(){ f := createFile("tmp/machinelearning.txt") defer closeFile(f) writeFile(f) } func createFile(p string) *os.File { fmt.Println("creating") f, err := os.Create(p) if err != nil{ panic(err) } return f } func closeFile(f *os.File){ fmt.Println("closing") err := f.Close() if err != nil{ fmt.Fprintf(os.Stderr, "error:%v\n",err) os.Exit(1) } } func writeFile(f *os.File){ fmt.Println("writing") fmt.Fprintln(f,"machine leanring") }
defer 語(yǔ)句會(huì)將函數(shù)推入到一個(gè)棧結(jié)構(gòu)中。同時(shí)棧結(jié)構(gòu)中的函數(shù)會(huì)在 return 語(yǔ)句執(zhí)行后被調(diào)用。
package main import "fmt" func main(){ // defer fmt.Println("word") // fmt.Println("hello") fmt.Println("hello") for i := 0; i <=3; i++ { defer fmt.Println(i) } fmt.Println("world") }
hello
world
3
2
1
0
可以通過(guò)在你的結(jié)構(gòu)上實(shí)現(xiàn) Error 接口中定義的 Error() 函數(shù)來(lái)實(shí)現(xiàn)自定義錯(cuò)誤類型,下面是一個(gè)例子。
Panic
panic 語(yǔ)句向 Golang 發(fā)出信號(hào),這時(shí)通常是代碼無(wú)法解決當(dāng)前的問(wèn)題,所以停止代碼的正常執(zhí)行流程。一旦調(diào)用了 panic,所有的延遲函數(shù)都會(huì)被執(zhí)行,并且程序會(huì)崩潰,其日志信息包括 panic 值(通常是錯(cuò)誤信息)和堆棧跟蹤。
舉個(gè)例子,當(dāng)一個(gè)數(shù)字被除以0時(shí),Golang會(huì)出現(xiàn) panic。
package main import "fmt" func main(){ divide(5) } func divide(x int){ fmt.Printf("divide(%d)\n",x+0/x) divide(x-1) }
divide(5) divide(4) divide(3) divide(2) divide(1) panic: runtime error: integer divide by zero goroutine 1 [running]: main.divide(0x0) /Users/zidea2020/Desktop/mysite/go_tut/main.go:10 +0xdb main.divide(0x1) /Users/zidea2020/Desktop/mysite/go_tut/main.go:11 +0xcc main.divide(0x2) /Users/zidea2020/Desktop/mysite/go_tut/main.go:11 +0xcc main.divide(0x3) /Users/zidea2020/Desktop/mysite/go_tut/main.go:11 +0xcc main.divide(0x4) /Users/zidea2020/Desktop/mysite/go_tut/main.go:11 +0xcc main.divide(0x5) /Users/zidea2020/Desktop/mysite/go_tut/main.go:11 +0xcc main.main() /Users/zidea2020/Desktop/mysite/go_tut/main.go:6 +0x2a exit status 2
Recover
Go語(yǔ)言提供了recover內(nèi)置函數(shù),前面提到,一旦panic,邏輯就會(huì)走到defer那,那我們就在defer那等著,調(diào)用recover函數(shù)將會(huì)捕獲到當(dāng)前的panic,被捕獲到的panic就不會(huì)向上傳遞了。然后,恢復(fù)將結(jié)束當(dāng)前的 Panic 狀態(tài),并返回 Panic 的錯(cuò)誤值。
package main import "fmt" func main(){ accessSlice([]int{1,2,5,6,7,8}, 0) } func accessSlice(slice []int, index int) { defer func() { if p := recover(); p != nil { fmt.Printf("internal error: %v", p) } }() fmt.Printf("item %d, value %d \n", index, slice[index]) defer fmt.Printf("defer %d \n", index) accessSlice(slice, index+1) }
包裝錯(cuò)誤
Golang 也允許對(duì)錯(cuò)誤進(jìn)行包裹,通過(guò)錯(cuò)誤嵌套,在原有錯(cuò)誤信息上添加一個(gè)額外信息幫助調(diào)用者對(duì)問(wèn)題判斷以及后續(xù)應(yīng)該如何處理信息。以通過(guò)使用 %w 標(biāo)志和 fmt.Errorf 函數(shù)來(lái)對(duì)原有的錯(cuò)誤進(jìn)行保存提供一些特定的信息,如下例所示。
package main import ( "errors" "fmt" "os" ) func main() { err := openFile("non-existing") if err != nil { fmt.Printf("error running program: %s \n", err.Error()) } } func openFile(filename string) error { if _, err := os.Open(filename); err != nil { return fmt.Errorf("error opening %s: %w", filename, err) } return nil }
上面已經(jīng)通過(guò)代碼演示如何包裝一個(gè)錯(cuò)誤,程序會(huì)打印輸出使用 fmt.Errorf 添加文件名的包裝過(guò)的錯(cuò)誤,也打印了傳遞給 %w 標(biāo)志的原有錯(cuò)誤信息。這里再補(bǔ)充一個(gè) Golang 還提供的功能,通過(guò)使用 error.Unwrap 來(lái)還原錯(cuò)誤信息,從而獲得原有的錯(cuò)誤信息。
package main import ( "errors" "fmt" "os" ) func main() { err := openFile("non-existing") if err != nil { fmt.Printf("error running program: %s \n", err.Error()) // Unwrap error unwrappedErr := errors.Unwrap(err) fmt.Printf("unwrapped error: %v \n", unwrappedErr) } } func openFile(filename string) error { if _, err := os.Open(filename); err != nil { return fmt.Errorf("error opening %s: %w", filename, err) } return nil }
錯(cuò)誤的類型轉(zhuǎn)換
有時(shí)候需要在不同的錯(cuò)誤類型之間進(jìn)行轉(zhuǎn)換,有情況需要通過(guò)類型轉(zhuǎn)換來(lái)為錯(cuò)誤添加信息,或者換一種表達(dá)方式,。 errors.As 函數(shù)提供了一個(gè)簡(jiǎn)單而安全的方法,通過(guò)尋找錯(cuò)誤鏈中匹配錯(cuò)誤類型進(jìn)行轉(zhuǎn)化輸出。如果沒(méi)有找到匹配的,該函數(shù)返回 false 。
package main import ( "errors" "fmt" "io/fs" "os" ) func main(){ // Casting error if _, err := os.Open("non-existing"); err != nil { var pathError *os.PathError if errors.As(err, &pathError) { fmt.Println("Failed at path:", pathError.Path) } else { fmt.Println(err) } } }
在這里,試圖將通用錯(cuò)誤類型轉(zhuǎn)換為 os.PathError ,這樣就可以訪問(wèn)該特定的錯(cuò)誤信息,這些信息保存在結(jié)構(gòu)體中的 Path 屬性上。
錯(cuò)誤類型檢查
Golang 提供了 errors.Is 函數(shù)來(lái)用于檢查錯(cuò)誤類型是否為指定的錯(cuò)誤類型,該函數(shù)返回一個(gè)布爾值值來(lái)表示是否為指定錯(cuò)誤類型。
package main import ( "errors" "fmt" "io/fs" "os" ) func main(){ // Check if error is a specific type if _, err := os.Open("non-existing"); err != nil { if errors.Is(err, fs.ErrNotExist) { fmt.Println("file does not exist") } else { fmt.Println(err) } } }
到此這篇關(guān)于golang 語(yǔ)言中錯(cuò)誤處理機(jī)制的文章就介紹到這了,更多相關(guān)golang 錯(cuò)誤處理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
golang實(shí)現(xiàn)mysql數(shù)據(jù)庫(kù)事務(wù)的提交與回滾
這篇文章主要介紹了golang實(shí)現(xiàn)mysql數(shù)據(jù)庫(kù)事務(wù)的提交與回滾,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-04-04Go語(yǔ)言之io.ReadAtLeast函數(shù)的基本使用和原理解析
io.ReadAtLeast函數(shù)是Go語(yǔ)言標(biāo)準(zhǔn)庫(kù)提供的一個(gè)工具函數(shù),能夠從數(shù)據(jù)源讀取至少指定數(shù)量的字節(jié)數(shù)據(jù)到緩沖區(qū)中,這篇文章主要介紹了io.ReadAtLeast函數(shù)的相關(guān)知識(shí),需要的朋友可以參考下2023-07-07GoFrame基于性能測(cè)試得知grpool使用場(chǎng)景
這篇文章主要為大家介紹了GoFrame基于性能測(cè)試得知grpool使用場(chǎng)景示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06一文詳解go中如何實(shí)現(xiàn)定時(shí)任務(wù)
定時(shí)任務(wù)是指按照預(yù)定的時(shí)間間隔或特定時(shí)間點(diǎn)自動(dòng)執(zhí)行的計(jì)劃任務(wù)或操作,這篇文章主要為大家詳細(xì)介紹了go中是如何實(shí)現(xiàn)定時(shí)任務(wù)的,感興趣的可以了解下2023-11-11