Go 语言的错误处理有两种方式:panic和error。
panic适用于严重的,调用方不可预料的错误,比如数组越界,即使告知调用方错误的原因,调用方也不可能在原地恢复程序正常流程。
error就是一般的错误,比如网络中断,调用方可以预料这种错误的发生,并能根据错误做出相应的处理。
有一种错误处理的模式是将panic通过recover转化成error,这种模式适用于内部错误多且杂,并且发生在各个函数调用中,但是对外只需要提供几种固定类型的错误即可。比如web server,内部错误或者是panic最终都要转化为4XX,5XX这样的应答。
代码示例:
func server(workChan <-chan *Work) {
for work := range workChan {
go safelyDo(work)
}
}
func safelyDo(work *Work) {
defer func() {
if err := recover(); err != nil {
log.Println("work failed:", err)
}
}()
do(work)
}
有一点需要注意:panic 之后只执行本routine中的语句。所以下边的代码并不能阻止程序的Crash。
func main() {
defer func() {
if err := recover(); err != nil {
fmt.Printf("recover outer: %s", err)
}
}()
go func() {
panic("panic in new routine")
}()
time.Sleep(2 * time.Second)
fmt.Println("go to here?")
}
更多的时候程序通过返回error来传递错误,但是error是一个接口类型,只能传递一个字符串出来。这对于调用者判断错误类型和更详细的调查错误原因是远远不够的。
简单一点的模式是设置全局的导出error变量(因为go中没有error类型的常量),但是这个变量的值永远不要变(正常的使用也没有变的必要),然后在接口中返回对应错误值,调用方使用==直接比较返回值和导出的error变量。
package search
var (
MyErrNotExist = errors.New("资源不存在")
)
func Find(name string) error {
return MyErrNotExist
}
search.Find函数可能还返回其他错误,调用方只要比较返回值是不是search.MyErrNotExist就可以判断错误类型。
还有一种方式是包提供err类型的判断方式,go语言的os包就是这么做的:
package main
import (
"fmt"
"os"
)
func main() {
filename := "a-nonexistent-file"
if _, err := os.Stat(filename); os.IsNotExist(err) {
fmt.Println("file does not exist")
}
}
对于大规模系统,仅仅返回error类型可能是不够的,还应该告知error发生在那个函数中,最好还有一个ID,能顺着ID找到一系列相关的上下文。《Concurrency in Go 》中给出一种自定义error实现:
type MyError struct{
Inner error
Message string
StackTrace string
Misc map[string]interface{}
}
func wrapError(err error, message string, msgArgs ...interface{}) MyError{
...
return
}
func (err MyError)Error()string{
return err.Message
}
首先MyError是一个error,这样保证了返回数据类型的兼容性;其次,Inner是一个error,能保存每一层调用的原始错误信息;StackTrace给出函数调用堆栈;Misc可以存放额外的自定义数据,比如requestID。这样当调用方收到一个error的时候,可以根据自己的需要决定是否深挖错误原因,还是封装错误给上层。