從Java、C++、PHP轉過來的Gopher在遇到錯誤處理時都會很苦惱,與前者們的相似try/catch模式相比,Golang的檢查返回值判斷錯誤的寫法顯得特別繁瑣。本文試圖去探究下Golang中Error Handling設計的背景與思惟過程,力求還原一個真實的設計權衡。
Golang標準包提供的Error Handling功能是經過error這個interface實現的。git
type error interface { Error() string }
因此自定義一個error類型很簡單,只要實現Error方法便可:github
// errorString is a trivial implementation of error. type errorString struct { s string } func (e *errorString) Error() string { return e.s }
那麼如何處理錯誤呢?
官方推薦使用相似於C語言的,檢查返回值的方式(if err != nil
),例如:golang
func readfile(path string) error { err := openfile(path) if err != nil { return fmt.Errorf("cannot open file: %v", err) } //... }
以上可能還看不出問題,好比下面這樣:spring
func DoSomething() (*C, error) { var err error var a string a, err =DoA() if err == nil { var b string b, err = DoB(a) if err == nil { var c string c, err = DoC(b) if err == nil { return c, nil } } } return nil, err }
每次調用一個函數都須要作一次錯誤檢查,這使得代碼寫起來十分繁瑣,而不像其餘編程語言中的try/catch來的簡潔:錯誤統一處理,開發者專一當前業務流程。編程
Golang的錯誤處理模式受到了許多批評的聲音,因而Rob Pike不得不在官網刊文解釋,並聲明『Errors are values』:Values can be programmed, and since errors are values, errors can be programmed.
,即開發者能夠自定義error類型,優雅地設計符合自身業務流程的錯誤處理方式。json
但同時他再次強調到:無論怎麼設計,在程序中檢查暴露出來的錯誤都是相當重要的。
一直到文章最後,Rob Pike都沒有解釋爲何要這麼設計,而是在闡述如何優雅地使用Error Handling。網絡
繼續搜索之,官方的FAQ道出了設計背後考慮:We believe that coupling exceptions to a control structure, as in the try-catch-finally idiom, results in convoluted code.
即官方認爲將異常耦合到控制結構(就像嘗試try-catch-finally習慣用法同樣)會致使複雜的代碼,並鼓勵開發者去顯示地檢查錯誤,這樣程序流程不會被打斷。這也是爲何Golang會提供多值返回的緣由之一。雖然與其餘編程語言不一樣, 但規範化的錯誤類型,加上Golang的其餘特性(如error可編程),使錯誤處理變得方便。編程語言
總結起來,Error Handling的設計初衷是拿編碼的冗餘性換取了邏輯的簡單性,這即是其中的tradeoff,正如 Go proverbs所言:Clear is better than clever.
ide
使用錯誤處理的姿式每每不止一種,標準庫裏經過的Error接口只夠知足通常簡單的場景,對於某些須要瞭解錯誤細節(如判斷錯誤是網絡中斷仍是返回值格式問題)以及當前執行棧的場景,則須要對Error接口的進一步封裝,目前比較推崇的是github.com/pkg/errors
包。函數
它除了輸出調用層級上每一級錯誤內容以外,還能夠打印出發生錯誤的文件名與所在行號,這對於定位Bug很是有幫助。
使用方法也很簡單,能夠看下面一個例子:
package main import ( "fmt" "github.com/pkg/errors" ) func A() error { return errors.New("NullPointerException") } func B() error { return A() } func main() { fmt.Printf("Error: %+v", B()) }
運行輸出:
彷佛開發者們已經習慣了try-catch-finally的用法,對於官方給出的Error Handling的設計解釋並不買帳,因而在Golang 2 草案中,Error Handling做爲一個重大改變被提了出來,並給出了優化方案:增長check
與handle
兩個新關鍵字。
這裏有兩個對比示例:
Go 1
type Parsed struct { ... } func ParseJson(name string) (Parsed, error) { // Open the file f, err := os.Open(name) if err != nil { return fmt.Errorf("parsing json: %s %v", name, err) } defer f.Close() // Parse json into p var p Parsed err = json.NewDecoder(f).Decode(&p) if err != nil { return fmt.Errorf("parsing json: %s %v", name, err) } return p }
Go 2
type Parsed struct { ... } func ParseJson(name string) (Parsed, error) { handle err { return fmt.Errorf("parsing json: %s %v", name, err) } // Open the file f := check os.Open(name) defer f.Close() // Parse json into p var p Parsed check json.NewDecoder(f).Decode(&p) return p }
對比能夠發現:增長了check與hanle關鍵字以後,總體的代碼邏輯變得更加簡潔,錯誤能夠統一在handle處獲得處理,相似於try/catch.
固然,做爲2.0的草案,並不必定會最終歸入Golang2的正式標準中,但如此好的設計值得期待。
https://blog.golang.org/error...
https://dave.cheney.net/paste...
https://blog.golang.org/error...
https://golang.org/doc/faq#ex...
https://medium.com/@hussachai...
https://blog.golang.org/go2draft
https://go.googlesource.com/p...
https://dev.to/deanveloper/go...