原文来自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代码。