golang 中的错误处理的哲学和 C 语言一样,函数通过返回错误类型(error)或者 bool 类型(不需要区分多种错误状态时)表明函数的执行结果,调用检查返回的错误类型值是否是 nil 来判断调用结果。

error

fmt.Errorferrors.New
type error interface {
    Error() string
}

C 语言的语法限制函数只能有一个返回值,函数的执行结果和执行成功时需要返回的信息都放到这个返回值里,具体的错误信息需要调用额外的接口获取。比如 C 标准库函数读取文件的函数 read 返回 -1 时表示读取错误,具体错误信息需要通过 errno 获取,返回 0 时,表示 EOF ,返回正整数时,表示成功读取到的字节数。golang 的多返回值语法糖避免了这种方式带来的不便,错误值一般作为返回值列表的最后一个,其他返回值是成功执行时需要返回的信息。为了避免错误处理时过深的代码缩进:

if err != nil {
	// error handling
} else {
	// normal code
}

推荐在发生错误时立即返回:

if err != nil {
	// error handling
	return // or continue, etc.
}
// normal code

虽然这种错误处理方式代码写起来会有一些冗长,但 golang 风格的代码应该尽量使用这种方式。

预定义错误值

errors.New()
func doStuff() error {
    if someCondition {
        return errors.New("no space left on the device")
    } else {
        return errors.New("permission denied")
    }
}
Error()
var ErrNoSpaceLeft = errors.New("no space left on the device")
var ErrPermissionDenied = errors.New("permission denied")

func doStuff() error {
    if someCondition {
        return ErrNoSpaceLeft
    } else {
        return ErrPermissionDenied 
    }
}

这样错误值判断就方便多了:

if err == ErrNoSpaceLeft {
    // handle this particular error
}
io.EOF

自定义错误类型

HTTP 表示客户端的错误状态码有几十个。如果为每种状态码都预定义相应的错误值,代码会变得很繁琐:

var ErrBadRequest = errors.New("status code 400: bad request")
var ErrUnauthorized = errors.New("status code 401: unauthorized")
// ...
Error()
type HTTPError struct {
    Code        int
    Description string
}

func (h *HTTPError) Error() string {
    return fmt.Sprintf("status code %d: %s", h.Code, h.Description)
}

这种方式下进行等值判断时需要转成具体的自定义类型然后取出 Code 字段判断:

func request() error {
    return &HTTPError{404, "not found"}
}

func main() {
    err := request()

    if err != nil {
        // an error occured
        if err.(*HTTPError).Code == 404 {
            // handle a "not found" error
        } else {
            // handle a different error
        }
    }

}
os.PathError

panic

golang 新手很容易把 panic 当成其他语言的异常(exception)导致 panic 的滥用。这里解释了为什么 golang 没有提供类似其他语言的异常机制的原因:主要是 try / catch 的方式处理错误过于复杂。使用 panic / recover 的机制也可以达到类似 try / catch 的效果,但是要特别谨慎的使用。
panic / recover 和 try / catch 机制最大的不同在于控制流程上的区别。try / catch 机制控制流作用在 try 代码块内,代码块执行到异常抛出点(throw)时,控制流跳出 try 代码块,转到对应的 catch 代码块,然后继续往下执行。panic / recover 机制控制流则作用在整个 goroutine 的调用栈。当 goroutine 执行到 panic 时,控制流开始在当前 goroutine 的调用栈内向上回溯(unwind)并执行每个函数的 defer 。如果 defer 中遇到 recover 则回溯停止,如果执行到 goroutine 最顶层的 defer 还没有 recover ,运行时就输出调用栈信息然后退出。所以如果要使用 recover 避免 panic 导致进程挂掉,recover 必须要放到 defer 里。
recover 调用的位置如果不对是无法将 panic 恢复的,这里有个比较复杂的规则,具体可以参考这里。为了避免过于复杂的代码,最好不要使用嵌套的 defer ,并且 recover 应该直接放到 defer 函数里直接调用。
panic 主要用于以下场景:

regexp.MustCompile

总结

Error()

参考资料