error标识符
概述
error是go预定义的标识符。任何实现error接口的都可以认为是一个error,此接口定义如下:
type error interface {
Error() string
}
所以,任何实现Error() string
方法的类型结构都可以作为error使用。
go标准库的errors包提供了创建error类型的函数New(),可以这样使用
func c(i int) error{
if i<0 {
return errors.New("i is a negative number")
}
return nil
}
我们看下new的实现:
package errors
func New(text string) error {
return &errorString{text}
}
type errorString struct {
s string
}
func (e *errorString) Error() string {
return e.s
}
new是text参数作为errors包下私有类似errorString
的初始化参数,Error()的指针接收者为errorString,也就是说,errorString类型实现了erorr的Error() string
方法,他就是一个error类型。new就是返回一个error类型值。
可以产生error类型值的另一个内建方式:
fmt.Errorf("%d is a negative number",i)
他也是通过errors.new 和Sprintf实现,并不会在屏幕打印字符串。
自定义
有时候,调用一个方法,我们需要根据返回error类型做不同的操作,比如http 4xx和5xx错误打印不同的日志等。可以定义自己的错误类型;自定义其实就跟new函数实现类似。
var e4xxError = errors.New("4xx")
func main() {
if err:=c(400);err == e4xxError{
fmt.Println(err)
return
}
fmt.Println("unknown err")
}
func c(i int) error{
if i<500 && i>=400 {
return e4xxError
}
return errors.New("no 4xx error")
}
但是使用==比较,只能是完全相同的变量值。这里通常指作为一个错误信号使用。
对于需要复杂的错误对象,比如error codes等,这种方式不太好,还是应该创建自己error实现类型结构,需要注意的是,返回值推荐设置为error类型,而非自定义错误类型。通常为了扩展复杂的错误信息,可以如下实现:
type Error interface {
error
Timeout() bool
Temporary() bool
}
调用时,通过类型断言err是不是net.Error,来细化错误的处理,比如使用switch语句判断。
if nerr, ok := err.(net.Error); ok && nerr.Temporary() {
time.Sleep(1e9)
continue
}
if err != nil {
log.Fatal(err)
}
压缩代码行数
d, err := F()
if err != nil {
fmt.Println(err)
return err
}
c, err := F1()
if err != nil {
fmt.Println(err)
return err
}
编程go时,经常有这种代码,每调用一次函数判断一次,可以换一种方式编写:
var d int
if d, err := F(); err != nil {
fmt.Println(err) // some int
return err
}
fmt.Println(d) // 0
统一处理
有时候,业务代码不需要没走不都判断错误,可以有一个统一错误处理方法解决;
- 把错误都定义在同一个包下;
// Kubernetes 源码
package errors
type StatusError struct {
ErrStatus metav1.Status
}
type APIStatus interface {
Status() metav1.Status
}
var _ error = &StatusError{}
// Error implements the Error interface.
func (e *StatusError) Error() string {
return e.ErrStatus.Message
}
func (e *StatusError) Status() metav1.Status {
return e.ErrStatus
}
func (e *StatusError) DebugError() (string, []interface{}) {
if out, err := json.MarshalIndent(e.ErrStatus, "", " "); err == nil {
return "server response object: %s", []interface{}{string(out)}
}
return "server response object: %#v", []interface{}{e.ErrStatus}
}
// error包下还可以这样定义一堆error
var ERR_EOF = errors.New("EOF")
var ERR_CLOSED_PIPE = errors.New("io: read/write on closed pipe")
var ERR_NO_PROGRESS = errors.New("multiple Read calls return no data or error")
var ERR_SHORT_BUFFER = errors.New("short buffer")
var ERR_SHORT_WRITE = errors.New("short write")
var ERR_UNEXPECTED_EOF = errors.New("unexpected EOF")
这个包下好友很多check函数,对于一直错误条件的check,可以直接返回bool类型。
func IsBadRequest(err error) bool {
return ReasonForError(err) == metav1.StatusReasonBadRequest
}
- 编写代码时,最后通过谓语句判断直接return,外层统一处理,比如:
// Kubernetes 源码
cmdutil.CheckErr(options.RunCreate(f, cmd))
统一在CheckErr()中处理。
异常panic
直接看一个示例:
func main() {
B()
fmt.Println("会执行.")
}
func A() {
fmt.Println("func A : do somethings")
// 出现问题,抛出异常(panic)
panic("not expect error")
fmt.Println("不会执行.")
}
func B() {
// defer 在前
defer func(){
if r := recover(); r != nil {
log.Println("捕获panic:",r)
}
}()
A()
fmt.Println("不会执行.")
}
func A : do somethings 会执行. 2018/03/16 11:49:20 捕获panic: not expect error
执行函数A,出现严重问题、致命错误时,使用panic。panic用于停止当前控制流程,并报告一个运行时恐慌/异常(panic)。之后的语句不会被执行,直接把控制权交给函数调用方,直到goroutine调用栈的最顶层。执行B,因为A出现panic,所以后面的语句不会执行,但是defer会执行,从而捕获panic。在main函数中,panic不再出现,所有B函数调用后的语句会被执行。
总结:
- panic 会根据函数的调用顺序逐层传递;
- panic 仅限于当前协程(routine),子协程抛出 panic,父协程无法捕获;
- 虽然子协程 panic 父协程无法捕获,但子协程的 panic 会导致父协程 crash(所以子协程最好捕获 panic),想要父协程获取子协程的 panic 信息,可以在子协程 recover 之后用 channel 传递出去;
参考:
[1]. go-best-practices-error-handling
[2]. error-handling-and-go
[3]. 错误处理
[4]. juju/errors
[5]. go-defer-panic-reover