在Go中創(chuàng)建自定義錯(cuò)誤的方式總結(jié)
引言
Go提供了兩種在標(biāo)準(zhǔn)庫中創(chuàng)建錯(cuò)誤的方法,[errors.New
和fmt.Errorf
],當(dāng)與用戶交流更復(fù)雜的錯(cuò)誤信息時(shí),或在調(diào)試時(shí)與未來的自己交流時(shí),有時(shí)這兩種機(jī)制不足以充分捕獲和報(bào)告所發(fā)生的情況。為了傳達(dá)更復(fù)雜的錯(cuò)誤信息并實(shí)現(xiàn)更多的功能,我們可以實(shí)現(xiàn)標(biāo)準(zhǔn)庫接口類型,error。
其語法如下:
type error interface { Error() string }
builtin包將error
定義為一個(gè)接口,它只有一個(gè)Error()
方法,返回一個(gè)字符串形式的錯(cuò)誤消息。通過實(shí)現(xiàn)這個(gè)方法,我們可以將定義的任何類型轉(zhuǎn)換為我們自己的error。
讓我們嘗試運(yùn)行以下示例來查看error
接口的實(shí)現(xiàn):
package main import ( "fmt" "os" ) type MyError struct{} func (m *MyError) Error() string { return "boom" } func sayHello() (string, error) { return "", &MyError{} } func main() { s, err := sayHello() if err != nil { fmt.Println("unexpected error: err:", err) os.Exit(1) } fmt.Println("The string:", s) }
Outputunexpected error: err: boom exit status 1
在這里,我們創(chuàng)建了一個(gè)新的空結(jié)構(gòu)類型MyError
,并在其上定義了Error()
方法。Error()
方法返回字符串"boom"
。
在main()
中,我們調(diào)用函數(shù)sayHello
,該函數(shù)返回一個(gè)空字符串和一個(gè)新的MyError
實(shí)例。由于sayHello
總是會(huì)返回錯(cuò)誤,所以main()
中的if語句體中的fmt.Println
調(diào)用總是會(huì)執(zhí)行。然后,我們使用fmt.Println
打印短前綴字符串"unexpected error:"
以及err
變量中保存的MyError
實(shí)例。
請注意,我們不必直接調(diào)用Error()
,因?yàn)?code>fmt包能夠自動(dòng)檢測這是Error
的實(shí)現(xiàn)。它透明地調(diào)用Error()
來獲取字符串"boom"
,并將其與前綴字符串"unexpected Error: err:"
連接起來。
在自定義錯(cuò)誤中收集詳細(xì)信息
有時(shí)候,自定義錯(cuò)誤是捕獲詳細(xì)錯(cuò)誤信息的最簡潔方式。例如,假設(shè)我們想要捕獲HTTP請求產(chǎn)生的錯(cuò)誤的狀態(tài)碼;運(yùn)行以下程序來查看error
的實(shí)現(xiàn),它允許我們清晰地捕獲信息:
package main import ( "errors" "fmt" "os" ) type RequestError struct { StatusCode int Err error } func (r *RequestError) Error() string { return fmt.Sprintf("status %d: err %v", r.StatusCode, r.Err) } func doRequest() error { return &RequestError{ StatusCode: 503, Err: errors.New("unavailable"), } } func main() { err := doRequest() if err != nil { fmt.Println(err) os.Exit(1) } fmt.Println("success!") }
Outputstatus 503: err unavailable
exit status 1
在這個(gè)例子中,我們創(chuàng)建了一個(gè)新的RequestError
實(shí)例,并使用標(biāo)準(zhǔn)庫中的errors.New
函數(shù)提供狀態(tài)碼和一個(gè)錯(cuò)誤。然后像前面的例子一樣,我們使用fmt.Println
打印它。
在RequestError
的Error()
方法中,我們使用fmt.Sprintf
函數(shù)來使用創(chuàng)建錯(cuò)誤時(shí)提供的信息來構(gòu)造一個(gè)字符串。
類型斷言和自定義錯(cuò)誤
error
接口只公開了一個(gè)方法,但我們可能需要訪問error
實(shí)現(xiàn)的其他方法來正確處理錯(cuò)誤。例如,我們可能有幾個(gè)臨時(shí)的error
自定義實(shí)現(xiàn),可以通過存在的 temporary()
方法進(jìn)行檢索。
接口為類型提供了更廣泛的方法集合,因此我們必須使用類型斷言來更改view正在顯示的方法,或者完全刪除它。
下面的例子在之前的RequestError
的基礎(chǔ)上增加了一個(gè)Temporary()
方法,該方法將表明調(diào)用者是否應(yīng)該重試請求:
package main import ( "errors" "fmt" "net/http" "os" ) type RequestError struct { StatusCode int Err error } func (r *RequestError) Error() string { return r.Err.Error() } func (r *RequestError) Temporary() bool { return r.StatusCode == http.StatusServiceUnavailable // 503 } func doRequest() error { return &RequestError{ StatusCode: 503, Err: errors.New("unavailable"), } } func main() { err := doRequest() if err != nil { fmt.Println(err) re, ok := err.(*RequestError) if ok { if re.Temporary() { fmt.Println("This request can be tried again") } else { fmt.Println("This request cannot be tried again") } } os.Exit(1) } fmt.Println("success!") }
Outputunavailable
This request can be tried again
exit status 1
在main()
中,我們調(diào)用doRequest()
,它會(huì)返回一個(gè)error
接口給我們。我們首先打印 error()
方法返回的錯(cuò)誤消息。接下來,我們嘗試使用類型斷言re, ok := err.(*RequestError)
來暴露RequestError
中的所有方法。如果類型斷言成功,那么我們使用Temporary()
方法來查看此錯(cuò)誤是否為臨時(shí)錯(cuò)誤。由于doRequest()
設(shè)置的StatusCode
是503
,這與http.StatusServiceUnavailable
匹配,因此返回true
并導(dǎo)致打印" this request can be try again"
。在實(shí)踐中,我們會(huì)發(fā)送另一個(gè)請求,而不是打印一條消息。
包裝錯(cuò)誤
通常,錯(cuò)誤會(huì)在程序之外產(chǎn)生,例如:數(shù)據(jù)庫、網(wǎng)絡(luò)連接等。這些錯(cuò)誤提供的錯(cuò)誤消息不能幫助任何人找到錯(cuò)誤的根源。在錯(cuò)誤消息的開頭使用額外的信息包裝錯(cuò)誤,可以為成功調(diào)試提供一些必要的上下文。
下面的例子演示了我們?nèi)绾螌⒁恍┥舷挛男畔⒏郊拥綇钠渌瘮?shù)返回的晦澀的error
上:
package main import ( "errors" "fmt" ) type WrappedError struct { Context string Err error } func (w *WrappedError) Error() string { return fmt.Sprintf("%s: %v", w.Context, w.Err) } func Wrap(err error, info string) *WrappedError { return &WrappedError{ Context: info, Err: err, } } func main() { err := errors.New("boom!") err = Wrap(err, "main") fmt.Println(err) }
Outputmain: boom!
WrappedError
是一個(gè)有兩個(gè)字段的結(jié)構(gòu)體:一個(gè)是string
類型的上下文消息,另一個(gè)是error
類型,WrappedError
提供了更多的信息。當(dāng)Error()
方法被調(diào)用時(shí),我們再次使用fmt.Sprintf
來打印上下文消息,然后Error
(fmt.Sprintf
也知道隱式調(diào)用Error()
方法)。
在main()
中,我們使用errors.New
創(chuàng)建一個(gè)錯(cuò)誤,然后使用我們定義的wrap
函數(shù)包裝這個(gè)錯(cuò)誤。這允許我們指出這個(gè)error
是在"main"
中生成的。此外,由于我們的WrappedError
也是一個(gè)error
,我們可以包裝其他的WrappedError
,這將允許我們看到一個(gè)鏈來幫助我們追蹤錯(cuò)誤的來源。在標(biāo)準(zhǔn)庫的幫助下,我們甚至可以在錯(cuò)誤中嵌入完整的堆棧跟蹤。
總結(jié)
由于error
接口只有一個(gè)方法,我們已經(jīng)看到我們可以為不同的情況提供不同類型的錯(cuò)誤。這可以涵蓋從將多條信息作為錯(cuò)誤的一部分進(jìn)行溝通到實(shí)現(xiàn)指數(shù)回退的所有事情。雖然Go中的錯(cuò)誤處理機(jī)制表面上看起來很簡單,但我們可以使用這些自定義錯(cuò)誤來處理常見和不常見的情況,從而實(shí)現(xiàn)相當(dāng)豐富的處理。
Go還有另一種溝通意外行為的機(jī)制panics。在錯(cuò)誤處理系列的下一篇文章中,我們將研究恐慌——它們是什么以及如何處理它們。
到此這篇關(guān)于在Go中創(chuàng)建自定義錯(cuò)誤的文章就介紹到這了,更多相關(guān)Go創(chuàng)建自定義錯(cuò)誤內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
go中的參數(shù)傳遞是值傳遞還是引用傳遞的實(shí)現(xiàn)
參數(shù)傳遞機(jī)制是一個(gè)重要的概念,它決定了函數(shù)內(nèi)部對參數(shù)的修改是否會(huì)影響到原始數(shù)據(jù),本文主要介紹了go中的參數(shù)傳遞是值傳遞還是引用傳遞的實(shí)現(xiàn),感興趣的可以了解一下2024-12-12Go初學(xué)者踩坑之go?mod?init與自定義包的使用
go?mod是go的一個(gè)模塊管理工具,用來代替?zhèn)鹘y(tǒng)的GOPATH方案,下面這篇文章主要給大家介紹了關(guān)于Go初學(xué)者踩坑之go?mod?init與自定義包的使用,需要的朋友可以參考下2022-10-10Go開源項(xiàng)目分布式唯一ID生成系統(tǒng)
這篇文章主要為大家介紹了Go開源項(xiàng)目分布式唯一ID生成系統(tǒng)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06go語言實(shí)現(xiàn)并發(fā)網(wǎng)絡(luò)爬蟲的示例代碼
本文主要介紹了go語言實(shí)現(xiàn)并發(fā)網(wǎng)絡(luò)爬蟲的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-03-03