Loading

错误、异常处理

十九、错误、异常处理

本章吐槽:go的异常处理实在是太难用了,一度让我写得十分反胃。

1 error

go中我们通常会在函数或方法中返回error结构对象来判断是否有异常出现。go内置的错误类型error是一个接口类型,自定义的错误类型必须实现它:

type error interface {
      Error() string
}

使用errors.New可返回一个错误信息:

err := errors.New("错误消息")

例:

func Sqrt(f float64) (float64, error) {
	if f < 0 {
		return 0, errors.New("被开方数不能为负")
	}
	return math.Sqrt(f), nil
}

func main() {
	d, err := Sqrt(-3)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println(d)
}

在上面代码中,我们定义一个简单的开方函数,如果被开方数为负就输出一个错误。

一般按照如下方法使用:

  • 如果函数需要处理异常,通常将error作为多值返回的最后一个值,返回的error值为nil则表示无异常,非nil则是有异常。
  • 一般先用if语句处理error!=nil,把正常逻辑写在后面。

另外你会发现,返回错误并不会终止程序的运行。

2 panic

error是错误,panic是异常。错误指的是可能出现问题的地方出现了问题,比如打开一个文件时失败,这种情况在人们的意料之中 ;而异常指的是不应该出现问题的地方出现了问题,比如引用了空指针,这种情况在人们的意料之外。可见,错误是业务过程的一部分,而异常不是 。与error不同,panic会终止程序的运行,在执行完所有的defer函数后,程序控制返回到该函数的调用方。这样的过程会一直持续下去,直到当前goroutine的所有函数都返回退出,然后程序会打印出 panic 信息,接着打印出堆栈跟踪(Stack Trace),最后程序终止。

func main() {
	fmt.Println("1")
	fmt.Println("2")
	panic("发生异常") // 主动抛异常,类似于java的throw,python的raise
	fmt.Println("3")
}

上述代码中,使用panic抛出异常后,第三个打印语句不会执行。

3 defer

defer是go语言中的延迟执行语句,用来添加函数结束时执行的代码。

func main() {
	defer fmt.Println("1")
	defer fmt.Println("2")
	fmt.Println("3")
}
/*结果为:
3
2
1
*/

当一个函数内多次调用defer时,go会把defer调用放入到一个栈中,在函数结束之前依次出栈,因此输出结果是逆序。

与panic结合使用:

func main() {
	defer fmt.Println("1")
	defer fmt.Println("2")
	panic("发生异常") // 主动抛异常,类似于java的throw,python的raise
	fmt.Println("3")
}

在抛出异常之前,再逆序执行所有的defer。

4 recover

panic会使后面的语句终止,recover可以让程序能正常运行下去。只有在defer函数的内部,调用 recover才有用。在defer函数内调用 recover,可以取到 panic的错误信息,并且停止 panic 续发事件(Panicking Sequence),程序运行恢复正常。recover的使用如下:

func f1() {
	fmt.Println("f1")
}

func f2() {
	defer func() { // 匿名函数,延迟执行
		if err := recover(); err != nil {
			fmt.Println(err) // 如果出错,恢复错误继续执行,并且打印错误
		}
		fmt.Println("无论是否有异常,此处永远都会执行")
	}()

	fmt.Println("f2")
	panic("抛出异常")

}

func main() {
	f1()
	f2()
}
posted @ 2021-12-13 22:31  yyyz  阅读(27)  评论(0编辑  收藏  举报