Go語言的有效錯誤處理
中午閑暇翻看Daniel Morsing的“The Go scheduler”時,發(fā)現(xiàn)其另外一篇短文“Effective error handling in Go”,文章不長,但感覺對Go中錯誤處理方法總結的還是比較到位的,這里譯之供大家參考。
一、簡介
Go語言受到詬病最多的一項就是其錯誤處理機制。如果顯式地檢查和處理每個error,這恐怕的確會讓人望而卻步。你可以試試這里列出的幾個方法,以避免你走入錯誤處理方法的誤區(qū)當中去。
二、在縮進區(qū)處理錯誤
當使用Go語言編寫代碼時,***下面這樣的錯誤處理方法:
- f, err := os.Open(path)
- if err != nil {
- // handle error
- }
- // do stuff
- 而不是下面這樣的:
- f, err := os.Open(path)
- if err == nil {
- // do stuff
- }
- // handle error
按照上面的方法處理錯誤,處理正常情況的代碼讀起來就顯得通篇連貫了。
三、定義你自己的errors
做好如何正確進行錯誤處理的***步就是要了解error是什么。如果你設計實現(xiàn)的包會因某種原因發(fā)生某種錯誤,你的包用戶將會對錯誤的原因很感興趣。為了滿足用戶的需求,你需要實現(xiàn)error接口,簡單做起來就像這樣:
- type Error string
- func (e Error) Error() string { return string(e) }
現(xiàn)在,你的包用戶通過執(zhí)行一個type assertion就可以知道是否是你的包導致了這個錯誤:
- result, err := yourpackage.Foo()
- if ype, ok := err.(yourpackage.Error); ok {
- // use ype to handle error
- }
通過這個方法,你還可以向你的包用戶暴露更多地結構化錯誤信息:
- type ParseError struct {
- File *File
- Error string
- }
- func (oe *ParseError) Error() string {//譯注:原文中這里是OpenError
- // format error string here
- }
- func ParseFiles(files []*File) error {
- for _, f := range files {
- err := f.parse()
- if err != nil {
- return &ParseError{ //譯注:原文中這里是OpenError
- File: f,
- Error: err.Error(),
- }
- }
- }
- }
通過這種方法,你的用戶就可以明確地知道到底哪個文件出現(xiàn)解析錯誤了。(譯注:從這里看到的go語言error設計之內(nèi)涵,讓我想起了Rob Pike大神的一篇Blog:"少即是級數(shù)級的多")
不過包裝error時要小心,當你將一個error包裝起來后,你可能會丟失一些信息:
- var c net.Conn
- f, err := DownloadFile(c, path)
- switch e := err.(type) {
- default:
- // this will get executed if err == nil
- case net.Error:
- // close connection, not valid anymore
- c.Close()
- return e
- case error:
- // if err is non-nil
- return err
- }
- // do other things.
如果你包裝了net.Error,上面這段代碼將無法知道是由于網(wǎng)絡問題導致的失敗,會繼續(xù)使用這條無效的鏈接。
有一條經(jīng)驗規(guī)則:如果你的包中使用了一個外部interface,那么不要對這個接口中方法返回的任何錯誤,使用你的包的用戶可能更關心這些錯誤,而不是你包裝后的錯誤。
四、將錯誤作為狀態(tài)
有時,當遇到一個錯誤時,你可能會停下來等等。這或是因為你將延遲報告錯誤,又或是因為你知道如果這次報告后,后續(xù)你會再報告同樣的錯誤。
***種情況的一個例子就是bufio包。當一個bufio.Reader遇到一個錯誤時,它將停下來保持這個狀態(tài),直到buffer已經(jīng)被清空。只有在那時它才會報告錯誤。
第二種情況的一個例子是go/loader。當你通過某些參數(shù)調(diào)用它導致錯誤時,它會停下來保持這個狀態(tài),因為它知道你很可能會使用同樣地參數(shù)再次調(diào)用它。
五、使用函數(shù)以避免重復代碼
如果你有兩段重復的錯誤處理代碼,你可以將它們放到一個函數(shù)中去:
- func handleError(c net.Conn, err error) {
- // repeated error handling
- }
- func DoStuff(c net.Conn) error {
- f, err := downloadFile(c, path)
- if err != nil {
- handleError(c, err)
- return err
- }
- f, err := doOtherThing(c)
- if err != nil {
- handleError(c, err)
- return err
- }
- }
優(yōu)化后的實現(xiàn)方法如下:
- func handleError(c net.Conn, err error) {
- if err == nil {
- return
- }
- // repeated error handling
- }
- func DoStuff(c net.Conn) error {
- defer func() { handleError(c, err) }()
- f, err := downloadFile(c, path)
- if err != nil {
- return err
- }
- f, err := doOtherThing(c)
- if err != nil {
- return err
- }
- }
這就是全部了。就Go語言錯誤處理而言,我知道的就這么多了。