摘要
本文内容转自网络,个人学习记录使用,请勿传播
Go 语言的异常处理语法绝对是独树一帜,一方面它鼓励你使用 C 语言的形式将错误通过返回值来进行传递,另一方面它还提供了高级语言一般都有的异常抛出和捕获的形式,但是又不鼓励你使用这个形式。后面我们统一将返回值形式的称为「错误」,将抛出捕获形式的称为「异常」。
错误接口
Go 语言规定凡是实现了错误接口的对象都是错误对象,这个错误接口只定义了一个方法。
1 | type error interface { |
注意这个接口的名称,它是小写的,是内置的全局接口。通常一个名字如果是小写字母开头,那么它在包外就是不可见的,不过 error 是内置的特殊名称,它是全局可见的。
编写一个错误对象很简单,写一个结构体,然后挂在 Error() 方法就可以了。
1 | package main |
Go 语言内置了一个通用错误类型,在 errors 包里。这个包还提供了一个 New() 函数让我们方便地创建一个通用错误。
1 | package errors |
错误处理
Go 语言里面不会抛异常,而是以返回值的形式来通知上层逻辑来处理错误
1 | package main |
os.Open()
、f.Read()
函数返回了两个值,Go 语言不但允许函数返回两个值,三个值四个值都是可以的- 只不过 Go 语言普遍没有使用多返回值的习惯,仅仅是在需要返回错误的时候才会需要两个返回值。
- 除了错误之外,还有一个地方需要两个返回值,那就是字典,通过第二个返回值来告知读取的结果是零值还是根本就不存在。
1 | var score, ok := scores["apple"] |
- 读文件操作
f.Read()
,它会将文件的内容往切片里填充,填充的量不会超过切片的长度(注意不是容量)。如果将缓冲改成下面这种形式,就会死循环!
1 | var buf = make([]byte, 0, 100) |
Redis 中错误处理
1 | go get github.com/go-redis/redis |
1 | package main |
因为 Go 语言中不轻易使用异常语句,所以对于任何可能出错的地方都需要判断返回值的错误信息。上面代码中除了访问 Redis 需要判断之外,字符串转整数也需要判断。
另外还有一个需要特别注意的是因为字符串的零值是空串而不是 nil,你不好从字符串内容本身判断出 Redis 是否存在这个 key 还是对应 key 的 value 为空串,需要通过返回值的错误信息来判断。代码中的 redis.Nil
就是客户端专门为 key 不存在这种情况而定义的错误对象。
defer
关键字
defer
关键字的作用是当外围函数返回之后才执行被推迟的函数。在文件输入输出操作中经常可以见到defer关键字,因为它使您不必记住何时关闭已打开的文件:defer关键字调用文件关闭函数关闭已打开的文件时,可以紧靠着文件打开函数之后。
defer函数在外围函数返回之后,以后进先出(LIFO)的原则执行。简单点说,在一个外围函数中有3个defer函数:f1()
最先出现,然后f2()
,最后f3()
,当外围函数执行返回之后,f3()
最先被执行,接着是f2()
,最后是f1()
。
1 | package main |
多个 defer 语句
有时候我们需要在一个函数里使用多次 defer 语句。比如拷贝文件,需要同时打开源文件和目标文件,那就需要调用两次 defer f.Close()
1 | package main |
需要注意的是 defer 语句的执行顺序和代码编写的顺序是反过来的,也就是说最先 defer 的语句最后执行,为了验证这个规则,我们来改写一下上面的代码
1 | package main |
异常与捕捉
Go 语言提供了 panic
和 recover
全局函数让我们可以抛出异常、捕获异常。它类似于其它高级语言里常见的 throw try catch
语句,但是又很不一样,比如 panic
函数可以抛出来任意对象。
严格来说,panic()
是一个内置的Go函数,它终止Go程序的当前流程并开始panicking
! 另一方面,recover()
函数也是一个内置的Go函数,允许你收回那些使用了panic()
函数的goroutine
的控制权。
1 | package main |
上面的代码抛出了 negErr,直接导致了程序崩溃,程序最后打印了异常堆栈信息。下面我们使用 recover 函数来保护它,recover 函数需要结合 defer 语句一起使用,这样可以确保 recover()
逻辑在程序异常的时候也可以得到调用。
1 | package main |
输出结果中的异常堆栈信息没有了,说明捕获成功了,不过即使程序不再崩溃,异常点后面的逻辑也不会再继续执行了。
panic 抛出的对象未必是错误对象,而 recover() 返回的对象正是 panic 抛出来的对象,所以它也不一定是错误对象。
1 | func panic(v interface{}) |
我们经常还需要对 recover() 返回的结果进行判断,以挑选出我们愿意处理的异常对象类型,对于那些不愿意处理的,可以选择再次抛出来,让上层来处理。
1 | defer func() { |
Go 语言官方表态不要轻易使用 panic recover,除非你真的无法预料中间可能会发生的错误,或者它能非常显著地简化你的代码。简单一点说除非逼不得已,否则不要使用它。
1 | package main |