Go Error Handling(1)

最后编辑于 2024-01-12

Go没有异常这类机制,与其他语言中的错误处理不太一样,而是用error这个built-in类型来表示错误。

error类型的定义

error是一个接口类型,在 universe block 中定义:

The universe block encompasses all Go source text.

1
2
3
type error interface {
    Error() string
}

即实现了Error() string方法的类型都可以作为error,最常用的实现是error包私有的errorString类型:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// errorString is a trivial implementation of error.
type errorString struct {
    s string
}

// 使用Error()即可获取错误信息:
func (e *errorString) Error() string {
    return e.s
}

// New returns an error that formats as the given text.
// 一般都用`error.New(str)`创建它:
func New(text string) error {
    return &errorString{text}
}

error处理技巧

有时Error()方法缺少一些重要信息,例如json.SyntaxError中的Error()连offset都没有提供,仅凭msg无法定位错误:

1
2
3
4
5
6
type SyntaxError struct {
	msg    string // description of error
	Offset int64  // error occurred after reading Offset bytes
}

func (e *SyntaxError) Error() string { return e.msg }

对于这种情况,可以使用type assertion来针对特定error类型获取其中的字段:

1
2
3
4
5
6
7
8
9
if err := dec.Decode(&val); err != nil {
    // 假如err是json.SyntaxError
    if serr, ok := err.(*json.SyntaxError); ok {
        // 获取报错时的Offset
        line, col := findLine(f, serr.Offset)
        return fmt.Errorf("%s:%d:%d: %v", f.Name(), line, col, err)
    }
    return err
}

error接口只有Error()一个方法,有时一些error实现会提供额外的方法,例如net.Error

1
2
3
4
5
6
7
package net

type Error interface {
    error
    Timeout() bool   // Is the error a timeout?
    Temporary() bool // Is the error temporary?
}

简化重复的错误处理

In Go, error handling is important. The language’s design and conventions encourage you to explicitly check for errors where they occur (as distinct from the convention in other languages of throwing exceptions and sometimes catching them). In some cases this makes Go code verbose, but fortunately there are some techniques you can use to minimize repetitive error handling.

Go鼓励在错误发生时就检查他们,某种情况下这使得Go代码显得有点臃肿,但幸运的是有些技巧可以简化错误处理

例如下面是一个http包的handler

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
func handler(w http.ResponseWriter, r *http.Request) {
    // content, err := funcMayReturnsError()
    // 错误处理
    // 此处也可以是err type assertion,为简化写成字符串判断
    if r.URL.RawQuery == "error1" {
        http.Error(w, "Some Error", 500)
    } else if r.URL.RawQuery == "error2" {
        http.Error(w, "Some Other Error", 500)
    } else {
        w.Write([]byte("Some Content"))
    }
}

// 注册handler
http.HandleFunc("/", handler)
// 运行server
http.ListenAndServe(":8080", nil)

在这个handler中按错误类型进行特定的处理,一个handler还好,如果写了几十个handler就会出现很多这类重复的错误处理代码,而且修改起来非常困难,如果能让handler返回错误再统一处理就会方便很多,但http.Handler不支持返回error的类型:

1
2
3
type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

为解决这个问题,可以通过以下技巧实现:

首先将handler改为返回error的格式,以便后续处理:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
func handler(w http.ResponseWriter, r *http.Request) error {
    // 业务代码
    content, err := funcMayReturnsError()

    if err != nil {
        // 正常响应
        w.Write([]byte("Some Content"))
    }

    return err
}

handler返回error后原先HandleFunc会报错,因为http.Handler并不支持返回错误的handler,这时声明一个appHandler

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// 先声明一个满足我们自定义handler的接口的类型
type appHandler func (w http.ResponseWriter, r *http.Request) error

// 为appHandler实现Handler接口
func (fn appHandler) ServeHttp(w http.ResponseWriter, r *http.Request) {
    if err := fn(w, r); err != nil {
        // 在这里集中处理错误
        http.Error(w, "Some Error", 500)
    }
}

http库的调用需要改为http.Handle,因为http.HandleFunc只支持传递匿名函数作为参数:

1
2
3
4
5
// 由于appHandler满足handler的接口,所以可以将handler转为appHandler类型
// 并传递给http.Handle
// ...其他代码省略
http.Handle("/", appHandler(handler))
http.ListenAndServe(":8080", nil)

改为这种设计后所有handler的错误都可以在appHandler类型的ServeHttp中处理,还可以让handler返回自定义的错误类型、定制错误的响应页面/格式、增加错误日志、获取backtrace、在appHandler对panic进行recover后统一处理等,十分方便

参考资料:

Error handling and Go