正确处理错误,可以让程序更加稳定。error是一个错误接口,一般来说,error是不会影响到整个程序运行的,我们可以自行处理。 而panic是会导致程序直接崩溃退出的,我们也可以用 go自带的recover()来恢复panic,在程序崩溃前进行资源的释放工作。

错误

error接口

error接口只有一个Error方法,返回具体的错误信息(string)

type error interface {
    Error() string
}

一个字符串转整数的例子:

func main(){
    // try to conver alpha "a" to integer
    i, err := strconv.Atoi("a")
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Println(i)
    }
}

尝试将字符 a 转换成为 整数,显然是不成功的,因此返回了err

一般而言,error 接口用于当方法或者函数执行遇到错误时进行返回,而且是第二个返回值。通过这种方式,可以让调用者自己根据错误信息决定如何进行下一步处理。

error 工厂函数

除了可以使用其他函数,自己定义的函数也可以返回错误信息给调用者

自定义error

自定义error就是先自定义一个新的类型,然后实现error接口

// 自定义一个结构体,携带错误码和具体的错误信息
type commonError struct {
    errorCode int	// 错误码
    errorMsg string // 错误信息
}

// 实现error接口
func (ce *commonError) Error() string {
    return ce.errorMsg
}

error 断言

func add(a, b int) int, error {
    if a < 0 || b < 0 {
        return 0, &commonError{
            errorCode: 1,
            errorMsg: "a and b must >= 0"
        }
    }
    return a + b, nil
}

func main(){
    sum, err := add(-1, 2)
    // 错误类型断言
    if cm, ok := err.(*commonError); ok {
        fmt.Printf("Error code = %d, error message = %s\n", cm.errorCode, cm.errorMsg)
    } else {
        fmt.Printf("Sum = %d\n", sum)
    }
}

错误嵌套 Error Wrapping

需求,将一个错误嵌套到另一个错误中:

比如调用一个函数,返回了一个错误信息 error,在不想丢失这个 error 的情况下,又想添加一些额外信息返回新的 error。这时候,我们首先想到的应该是自定义一个 struct,如下面的代码所示:

type MyError struct {
    err error	// 指向原有的error
   	msg string	// 当前error的错误信息
}

// 上述就是错误的嵌套
// MyError 实现error接口,返回当前错误信息,和原有错误信息
func (me *MyError) Error() string {
    return fmt.Sprintf("Wrapped error: %s -> %s", me.err.Error(), me.msg)
}

func main(){
    newError := MyError{err, "New error message."}
}

上述实现方式比较繁琐,从Go 1.13开始,go标准库新增了Error Wrapping功能,我们可以使用 fmt.Errorf 加 %w 的方式,实现错误的嵌套

e := errors.New("Original Error")
w := fmt.Errorf("Wrapped Error: %w", e)
fmt.Println(w)

errors.Unwrap 函数

通过 errors.Unwrap 解开嵌套的错误

errors.Unwrap(w) 	// w 是一个嵌套的错误,会返回嵌套前的原始错误
errors.Unwrap(e)	// e 是一个原始的错误,会返回一个nil

errors.Is 函数

使用 Error Wrapping 之后,原来使用 == 判断是否同一个错误的方式失效了

使用 errors.Is

func Is(err, target error) bool
  • 如果err和target 是同一个,返回true
  • 如果err是一个wrapping error,且target也包含在这个嵌套的error链中的话,也返回true

errors.As 函数

errors.As是嵌套error的断言

var cm *commonError
if errors.As(err,&cm){
   fmt.Println("错误代码为:",cm.errorCode,",错误信息为:",cm.errorMsg)
} else {
   fmt.Println(sum)
}

Deffered 函数

defer 函数用于保证打开的文件一定会被关闭,功能有点类似于 python 的 with

defer 的执行关系是先调用后执行

defer func() {fmt.Println("A")}()
defer func() {fmt.Println("B")}()
defer func() {fmt.Println("C")}()
// 最后输出C、B、A

defer 有一个调用栈,越早定义越靠近栈的底部,越晚定义月靠近栈的顶部,执行这些defer语句的时候,会先从栈顶弹出一个defer然后执行它。

Panic 异常

go语言是一门静态的强类型语言,很多问题都尽可能地在编译是捕获,但是有一些只能在运行时检查,比如数组越界访问、不相同的类型强制转换等,这类运行时问题都会引起panic异常。

panic异常直接将运行中的程序中断

panic可以接受 interface{}类型的参数(空接口),任何类型的值都可以传到panic

func panic(v interface{})

panic 异常是一种非常严重的情况,会让程序中断运行,使程序崩溃,所以如果是不影响程序运行的错误,不要使用 panic,使用普通错误 error 即可

使用Recover捕获panic异常

通常情况下,不需要对panic进行处理,直接让其中断程序即可

但当我们需要在程序崩溃前做一些资源释放处理时,这时就得从panic恢复,然后进行处理

我们可以使用go语言内置的recover函数恢复panic异常,只有defer修饰的函数才能在程序崩溃时执行,所以:

recover函数要结合defer关键字使用才能生效

func connectMysql(ip, username, password string) {
    if ip == "" {
        panic("Invalid ip, expect not empty string.")
    } else {
        fmt.Printf("Connecting to ip[] with %s@%s\n", ip, username, password)
    }
}

func main(){
    defer func(){
        if p := recover(); p != nil {
            fmt.Printf("go panic: %v", p)
        }
    }()
    connectMysql("", "root", "root")
}