1. panic 与 error
1.1 panic
panic 会中止程序执行进入异常处理逻辑,panic可以在当前函数或者调用链向上的任何一层被defer recover 捕获处理,没被捕获的panic会使程序打印堆栈后异常退出。
panic很少在代码中出现,一般用来表示除0、内存不足、强制类型转换失败等语言层级的异常,如果出现意味着程序员本身的实现问题,所以程序员一般不会着重考虑和防御它们。
1.2 error
error会显得更平凡,所有满足error这个interface的值都可以当做一个合法的错误:
1type error interface {
2 // 输出对该错误的文本描述
3 Error() string
4}
在Go语言中,error是一个值,运行时不会用特殊的逻辑对待它,不处理它不会让程序打印堆栈异常退出。
error会在代码中大量出现,表示连接超时、JSON解析失败、文件不存在等用户层级的错误,往往和业务直接相关,程序需要着重考虑它们。
1.3 总结
- panic:不着重考虑,没有明确的抛出位置;
- error:着重考虑,抛出位置固定而明显,只要函数返回值元组中最后一个是error类型,用户就需要考虑防御和处理。
换句话说,Go语言中error是值这个特征在鼓励用户考虑和处理每个错误,写出鲁棒的程序。
2. “好”的错误
例如,有个天气API服务,它在收到请求时会先查询Redis中有没有对应缓存,如果没有缓存再请求外部服务,最后返回结果给调用方。
有一天,调用方说接口不工作了,你查了日志,错误信息是 context deadline exceeded
,这个错误意味着代码里有地方发生了超时,可是在哪里呢?是缓存还是外部API?
好的错误是有根本原因和调用链上下文的错误,这样的错误容易排查,如果你看到的错误信息是这样的:
1reading cache: redis GET: context deadline exceeded
或者带有调用堆栈的:
1context deadline exceeded
2goroutine 1 [running]:
3main.Example(0x19010001)
4 /Users/hello/main.go
5 temp/main.go:8 +0x64
6main.main()
7 /Users/bill/main.go
8 temp/main.go:4 +0x32
那么排错工作都会容易得多。
比起堆栈,人工添加的文本错误上下文更易于阅读,有着更高的信息密度,而且看到的人就算没有接触相关代码也有可能理解错误原因。
3. 为错误提供文本上下文
Go 1.13中新增了fmt.Errorf()
用于为错误提供上下文,errors标准库新增Is()
, As()
, Unwrap()
用于便利化错误的鉴别和比较。
fmt.Errorf()
的用法如下:
1reading cache: redis GET: context deadline exceeded
这个错误信息从左到右层层递进,每层fmt.Errorf()
叙述自己想要做的事情,然后用:
分隔下一层,下一层用%w
指代。
下面这段伪代码中,FindWeather()
调用ReadCache()
,请试想ReadCache()
报错时,FindWeather()
调用者收到的错误:
1func FindWeather(city string) (weather string, err error) {
2 weather, err = ReadCache(city)
3 if err != nil {
4 err = fmt.Errorf("reading cache: %w", err)
5 return
6 }
7
8 if weather != "" {
9 // cache hit
10 return
11 }
12
13 // cache missed, query for data source and update cache
14 // ...
15}
16
17func ReadCache(city string) (weather string, err error) {
18 cacheKey := "city-" + city
19 weather, err = cache.Get(cacheKey)
20 if err == redis.Nil {
21 // cache missed
22 err = nil
23 return
24 } else if err != nil {
25 err = fmt.Errorf("redis Get: %w", err)
26 return
27 }
28
29 return
30}
Go语言的静态分析工具能检查出代码中忘记处理的错误,不规范的错误上下文格式等(%d
,%f
, %v
, %w
, %x
分不清),一般使用它们的合集版本golangci-lint.
3.1 其他为错误添加上下文的方式
如果有必要,也可以使用其他为错误添加上下文的方案
- 使用pkg/errors为错误添加堆栈;
- 如果是HTTP服务,使用panic和recover中间件,知乎的实现就是个例子,我猜想上下文也是以堆栈的方式呈现。