11.1 错误处理 ¶
Go语言主要的设计准则是:简洁、明白,简洁是指语法和C类似,相当的简单,明白是指任何语句都是很明显的,不含有任何隐含的东西,在错误处理方案的设计中也贯彻了这一思想。我们知道在C语言里面是通过返回-1或者NULL之类的信息来表示错误,但是对于使用者来说,不查看相应的API说明文档,根本搞不清楚这个返回值究竟代表什么意思,比如:返回0是成功,还是失败,而Go定义了一个叫做error的类型,来显式表达错误。在使用时,通过把返回的error变量与nil的比较,来判定操作是否成功。例如os.Open
函数在打开文件失败时将返回一个不为nil的error变量
func Open(name string) (file *File, err error)
os.Open
打开一个文件,如果出现错误,那么就会调用log.Fatal
来输出错误信息: f, err := os.Open("filename.ext")
if err != nil {
log.Fatal(err)
}
os.Open
函数,标准包中所有可能出错的API都会返回一个error变量,以方便错误处理,这个小节将详细地介绍error类型的设计,和讨论开发Web应用中如何更好地处理error。 Error类型 ¶
error类型是一个接口类型,这是它的定义:
type error interface {
Error() string
}
// errorString is a trivial implementation of error.
type errorString struct {
s string
}
func (e *errorString) Error() string {
return e.s
}
errors.New
把一个字符串转化为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, errors.New("math: square root of negative number")
}
// implementation
}
f, err := Sqrt(-1)
if err != nil {
fmt.Println(err)
}
自定义Error ¶
通过上面的介绍我们知道error是一个interface,所以在实现自己的包的时候,通过定义实现此接口的结构,我们就可以实现自己的错误定义,请看来自Json包的示例:
type SyntaxError struct {
msg string // 错误描述
Offset int64 // 错误发生的位置
}
func (e *SyntaxError) Error() string { return e.msg }
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
}
func Decode() *SyntaxError { // 错误,将可能导致上层调用者err!=nil的判断永远为true。
var err *SyntaxError // 预声明错误变量
if 出错条件 {
err = &SyntaxError{}
}
return err // 错误,err永远等于非nil,导致上层调用者err!=nil的判断始终为true
}
上面例子简单的演示了如何自定义Error类型。但是如果我们还需要更复杂的错误处理呢?此时,我们来参考一下net包采用的方法:
package net
type Error interface {
error
Timeout() bool // Is the error a timeout?
Temporary() bool // Is the error temporary?
}
if nerr, ok := err.(net.Error); ok && nerr.Temporary() {
time.Sleep(1e9)
continue
}
if err != nil {
log.Fatal(err)
}
错误处理 ¶
Go在错误处理上采用了与C类似的检查返回值的方式,而不是其他多数主流语言采用的异常方式,这造成了代码编写上的一个很大的缺点:错误处理代码的冗余,对于这种情况是我们通过复用检测函数来减少类似的代码。
请看下面这个例子代码:
func init() {
http.HandleFunc("/view", viewRecord)
}
func viewRecord(w http.ResponseWriter, r *http.Request) {
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 {
http.Error(w, err.Error(), 500)
return
}
if err := viewTemplate.Execute(w, record); err != nil {
http.Error(w, err.Error(), 500)
}
}
http.Error
,返回给客户端500错误码,并显示相应的错误数据。但是当越来越多的HandleFunc加入之后,这样的错误处理逻辑代码就会越来越多,其实我们可以通过自定义路由器来缩减代码(实现的思路可以参考第三章的HTTP详解)。 type appHandler func(http.ResponseWriter, *http.Request) error
func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if err := fn(w, r); err != nil {
http.Error(w, err.Error(), 500)
}
}
func init() {
http.Handle("/view", appHandler(viewRecord))
}
func viewRecord(w http.ResponseWriter, r *http.Request) error {
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 err
}
return viewTemplate.Execute(w, record)
}
type appError struct {
Error error
Message string
Code int
}
type appHandler func(http.ResponseWriter, *http.Request) *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)
}
}
func viewRecord(w http.ResponseWriter, 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
}
总结 ¶
在程序设计中,容错是相当重要的一部分工作,在Go中它是通过错误处理来实现的,error虽然只是一个接口,但是其变化却可以有很多,我们可以根据自己的需求来实现不同的处理,最后介绍的错误处理方案,希望能给大家在如何设计更好Web错误处理方案上带来一点思路。
links ¶
- 目录
- 上一节: 错误处理,调试和测试
- 下一节: 使用GDB调试