Golang有很多优点,这也是它如此流行的主要原因。但是Go 1 对错误处理的支持过于简单了,以至于日常开发中会有诸多不便利。
除了被广泛吐槽的 `if err != nil` 之外, 就连其改进路线也备受争议、分歧明显,以致于一个改进提案都会因为压倒性的反对意见而不得不作出调整。
**建议使用 github.com/pkg/errors 进行错误处理。**
### 问题
---
Golang开发中经常需要检查返回的错误值并作相应处理。最简示例如下:
```golang
import (
"database/sql"
"fmt"
)
func foo() error {
return sql.ErrNoRows
}
func bar() error {
return foo()
}
func main() {
err := bar()
if err != nil {
fmt.Printf("got err, %+v\n", err)
}
}
//Outputs:
// got err, sql: no rows in result set
```
有时需要根据返回的error类型作不同处理,例如:
```golang
import (
"database/sql"
"fmt"
)
func foo() error {
return sql.ErrNoRows
}
func bar() error {
return foo()
}
func main() {
err := bar()
if err == sql.ErrNoRows {
fmt.Printf("data not found, %+v\n", err)
return
}
if err != nil {
// Unknown error
}
}
//Outputs:
// data not found, sql: no rows in result set
```
实践中经常需要为错误增加上下文信息后再返回,以方便调用者了解错误场景。例如 foo方法时常写成
```golang
func foo() error {
return fmt.Errorf("foo err, %v", sql.ErrNoRows)
}
```
但这时 `err == sql.ErrNoRows` 便不再成立。除此之外,上述写法都在返回错误时都丢掉了调用栈这个重要的诊断信息。我们需要更灵活、更通用的方式来应对此类问题。
### 解决方案
---
`github.com/pkg/errors`
来自 [Dave Cheney](https://dave.cheney.net/) , 有三个关键方法
1. `Wrap` 方法用来包装底层错误,增加上下文文本信息并附加调用栈。 一般用于包装对第三方代码(标准库或第三方库)的调用。
2. `WithMessage` 方法仅增加上下文文本信息,不附加调用栈。 如果确定错误已被 `Wrap` 过或不关心调用栈,可以使用此方法。 注意:不要反复 `Wrap` ,会导致调用栈重复。
3. `Cause`方法用来判断底层错误 。
用这个包重写上述程序:
```golang
import (
"database/sql"
"fmt"
"github.com/pkg/errors"
)
func foo() error {
return errors.Wrap(sql.ErrNoRows, "foo failed")
}
func bar() error {
return errors.WithMessage(foo(), "bar failed")
}
func main() {
err := bar()
if errors.Cause(err) == sql.ErrNoRows {
fmt.Printf("data not found, %v\n", err)
fmt.Printf("%+v\n", err)
return
}
if err != nil {
// unknown error
}
}
/*Output:
data not found, bar failed: foo failed: sql: no rows in result set
sql: no rows in result set
foo failed
main.foo
/usr/three/main.go:11
main.bar
/usr/three/main.go:15
main.main
/usr/three/main.go:19
runtime.main
...
*/
```
从输出内容可以看到, 使用 `%v` 作为格式化参数,那么错误信息会保持一行, 其中依次包含调用栈的上下文文本。 使用 `%+v` ,则会输出完整的调用栈详情
如果不需要增加额外上下文信息,仅附加调用栈后返回,可以使用 `WithStack` 方法:
```golang
func foo() error {
return errors.WithStack(sql.ErrNoRows)
}
```
>注意:无论是 Wrap , WithMessage 还是 WithStack ,当传入的 err 参数为 nil 时, 都会返回nil, 这意味着我们在调用此方法之前无需作 nil 判断,保持了代码简洁
Golang 异常处理