原文來自Error handling and Gogit
若是你有寫過Go代碼,那麼你能夠會遇到Go中內建類型error。Go語言使用error*值來顯示異常狀態。例如,os.Open在打開文件錯誤時,會返回一個非nil error值。github
func Open(name string) (file *File, err error)
下面的代碼使用os.Open來打開一個文件。若是出現錯誤,會調用log.Fatal打印出錯誤的信息而且終止代碼。golang
f, err := os.Open("filename.etx") if err != nil { log.Fatal(err) } // do something with the open *File f
在使用Go的工做中,上面的例子已經能知足大多數狀況,可是這篇文章會更進一步的探討關於捕獲異常的實踐。數據庫
error類型是一個interface類型。一個error變量能夠經過任何能夠描述本身的string類型的值來展現本身。下面是它的接口描述:json
type error interface { Error() String }
error類型,就像其餘內建類型同樣,==是在全局中預先聲明的==。這意味着咱們不用導入就能夠在任何地方使用它。網絡
最經常使用的error實現是在 errors 包中定義的一個不可導出的類型:errorString。app
// errorString is a trivial implementation os error. type errorString struct { s string } func (e *errorString) Error() string { return e.s }
經過errors.New函數能夠建立一個errorString實例.該函數接收一個string參數,並將string參數轉換爲一個erros.errorString,而後返回一個error值.函數
// New returns an error that formats as the given text. func New(text string) error { return &errorString{text} }
下面是如何使用errors.New的例子調試
func Sqrt(f float64) (float64, error) { if f < 0 { return 0, error.New("math: squara root of negative number") } // implementation }
在調用Sqrt時,若是傳入的參數是負數,調用者會接收到Sqrt返回的一個非空error值(正確來講應該是一個errors.errorString值)。調用者能夠經過調用error的Error方法或者經過打印來獲得錯誤信息字段("math: squara root of nagative number")。code
f, err := Sqrt(-1) if err != nil { fmt.Println(err) }
fmt包經過調用Error()方法來格式化error值
一個error接口的責任是總結錯誤的內容。os.Open的錯誤返回的格式是像"open /etc/passwd: permission denied"這樣的格式, 而不只僅只是"permission denied"。Sqrt返回的錯誤缺乏了關於非法參數的信息。
爲了讓信息更加明確,比較好用的一個函數是fmt包裏面的Errorf。它根據Printf的規則來函格式化一個字符串而且返回,就像使用errors.New建立的error值。
if f < 0 { return 0, fmt.Errorf("math: square root of negative number %g", f) }
不少狀況下,fmt.Errorf已經可以知足咱們了,可是有時候咱們還須要更多的細節。咱們知道error是一個接口,所以你能夠定義任意的數據類型來做爲error值,以供調用者獲取更多的錯誤細節。
例如,若是有一個比較複雜的調用者想要恢復傳給Sqrt的非法參數。咱們經過定義一個新的錯誤實現而不是使用errors.errorString來實現這個需求:
type NegativeSqrtError float64 func (f NegativeSqrtError) Error() string { return fmt.Sprintf("math: square root of negative number %s", float64(f)) }
一個複雜的調用者就可使用類型斷言(type assertion)來檢測NegativeSqrtError而且捕獲它,與此同時,對於使用fmt.Println或者log.Fatal來輸出錯誤的方式來講卻沒有改變他們的行爲。
另外一個例子來自json包,當咱們在使用json.Decode函數時,若是咱們傳入了一個不合格的JSON字段,函數返回SyntaxError類型錯誤。
type SyntaxError struct { msg string // description of error Offset int64 // error occurred after reading Offset bytes } func (e *SyntaxError) Error() string { return e.msg }
咱們能夠看到, Offset甚至尚未在默認的error的Error函數中出現,可是調用者能夠用它來生成帶有文件名和行號的錯誤信息。
if err := dec.Decode(&val); err != nil { if serr, ok := err.(*json.SyntaxError); ok { line, col := findLine(f, serr.Offset) return fmt.Errorf("%s:%d:%d: %v", f.Name(), line, col, err) } return err }
(這是項目Camlistore中的代碼的一個簡化版實現)
內置的error接口只須要實現Error方法;特定的error實現可能會添加其餘的一些附加方法。例如net包, net包內有不少種error類型,一般跟經常使用的error同樣,可是有些error實現添加一些附加方法,這些附加方法經過net.Error接口定義:
package net type Error interface { error Timeout() bool // Is the error a timeout? Temporary() bool // Is the error temporary? }
客戶端代碼能夠經過類型斷言來檢測一個net.Error錯誤以區分這是一個暫時性錯網絡誤仍是一個永久性錯誤。例如當一個網絡爬蟲遇到一個錯誤時,若是是暫時性錯誤,它會睡眠一下而後在重試,不然中止嘗試。
if nerr, ok := err.(net.Error); ok && nerr.Temporary() { time.Sleep(1e9) continue } if err != nil { log.Fatal(err) }
Go中,錯誤捕獲是很重要的。Go的語言特性和使用習慣鼓勵你在錯誤發生時作出明確的檢測(這和那些拋出異常的而後有時捕獲他們的語言有些區別)。在某些狀況,這種方式會形成Go代碼的冗餘,不過幸運的是咱們能使用一些技術來減小這種重複的捕獲操做。
考慮這樣一個App應用,這個應用有一個HTTP的處理函數,用來從數據庫接收數據而且將數據用模板格式化。
func init() { http.HandleFunc("/view", viewRecord) } func viewRecord(w http.ResponseWriter, r *http.Request) { c := appengin.NewContext(r) key := datastore.NewKey(c, "Record", r.FormatValue("id"), 0, nil) record := new(Record) if err := datastore.Get(c, key, record); err != nil { http.Error(w, err.Error(), 500) return } if err := viewTemplate.Execute(w, record); err != nil { http.Error(w, err.Error(), 500) } }
這個函數捕獲從datastore.Get函數和viewTemplate.Excute方法返回的錯誤。這兩種狀況都返回帶Http狀態碼爲500的簡單的錯誤信息。上面的代碼看起來也很少,能夠接受,可是若是添加更多的 HTTP handlers狀況就不同了,你立刻會發現不少這樣的重複代碼來處理這些錯誤。
爲了減小這些重複的錯誤處理代碼,咱們能夠定義咱們本身的 HTTP AppHandler,讓它成一個帶着error返回值的類型:
type appHandler func(http.ResponseWriter, *http.Request) error
而後咱們能夠更改viewRecord函數,讓它將錯誤返回:
fun viewRecord(w http.ResponseWriter, r *http.Request) error { c := appending.NewContext(r) key := datastore.NewKey(c, "Record", r.FormValie("id"), 0, nil) record := new(Record) if err := datastore.Get(c, key, record); err != nil { return err } return viewTemplate.Execute(w, record) }
這看起來比原始版本代碼的簡單了些, 可是 http 包並不能理解viewRecord函數返回的錯誤。這時咱們能夠經過實如今appHandler上的 http.Handler接口的方法 ServerHTTP來解決這個問題:
func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if err := fn(w, r); err != nil { http.Error(w, err.Error(), 500) } }
ServeHTTP方法調用appHandler方法而且將返回的錯誤展現給用戶。注意,ServeHTTP方法的接受者是一個函數。(go語言容許這樣作)這個方法經過表達式fn(w, r)來調用他的接受者,使ServeHTTP和appHandler關聯在一塊兒
如今,咱們在http包中註冊viewRecord時,使用了Hanlder函數(而不是HandlerFunc)。由於如今appHandler是一個http.Handler(而不是 http.HandlerFunc)。
func init() { http.Handle("/view", appHander(viewRecord)) }
經過構建一個特定的error做爲基礎構建,咱們可讓咱們的錯誤對用戶更友好。相對於僅僅將錯誤字符串展現給出來,返回帶有HTTP狀態碼的錯誤字符串是一個更好的展現方式,而且還能記錄下全部的錯誤信息以供App開發者調試用。
下面的代碼展現如何實現這種需求。咱們建立了一個包含error類型的和其餘類型的字段的appError結構體
type appError struct { Error error Message string Code int }
下一步咱們修改appHandler類型,讓它返回 *appError值:
type appHandler func(http.ResponseWriter, *http.Request) * appError
(一般,相對於返回一個error返回一個特定類型的錯誤是不對的,具體緣由能夠參考Go FQA , 可是在這裏是正確的,由於這個錯誤值只有ServeHTTP會用到它)
而後咱們讓appHandler的ServeHTTP方法將帶着HTTP狀態碼的appError錯誤信息展現給用戶,而且將全部錯誤信息展現給開發者終端。
func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if e := fn(w, r); e != nil { // e is *appError, not os.Error. c := appengine.NewContext(r) c.Errorf("%v", e.Error) http.Error(w, e.Message, e.Code) } }
最後,咱們更新viewRecord的代碼,讓它遇到錯誤時返回更多的內容:
func viewRecord(w http.ResponseWrite, r *http.Request) *appError { c := appengine.NewContext(r) key := datastore.NewKey(c, "Record", r.FormValue("id"), 0, nil) record := new(Record) if err := datastore.Get(c, key, record); err != nil { return &appError{err, "Record not found", 404} } if err := viewTemplate.Execute(w, record); err != nil { return &appError(err, "Can't display record", 500) } return nil }
這個版本的viewRecord跟原始版本有着相同的長度,可是如今這些放回信息都有特殊的信息,咱們提供了更爲友好的用戶體驗。
固然,這還不是最終的方案,咱們還能夠進一步提高咱們的application中的error處理方式。下面是改進的一些點:
適合的錯誤處理是一個好軟件最基本的要求。經過這篇文章中討論的技術,你應該能寫出更加可靠簡介的Go代碼。