错误处理
Go 语言通过内置的错误接口提供了非常简单的错误处理机制
error 类型是一个接口类型,定义如下
type error interface {
Error() string
}
/* 使用 errors.New 返回错误信息 */
erros.New("error:is a error info")
/* 使用 fmt.Errorf 返回错误信息 */
fmt.Errorf("decompress %v: %v", name, err)
Go 1.13 内置支持
import (
"database/sql"
"errors"
"fmt"
)
func bar() error {
if err := foo(); err != nil { // 需要判断错误是否为空
return fmt.Errorf("bar failed: %w", foo()) // 引入了一个新的 fmt 格式化动词: %w,使用 Is/As 进行判断
}
return nil
}
func foo() error {
return fmt.Errorf("foo failed: %w", sql.ErrNoRows) // 引入了一个新的 fmt 格式化动词: %w,使用 Is/As 进行判断
}
func main() {
err := bar()
if errors.Is(err, sql.ErrNoRows) { // 使用Is函数,类似 哨兵错误(sentinel error)比较
fmt.Printf("data not found, %+v\n", err)
return
}
var ErrSql *sql.ErrNoRows
if errors.As(err, &ErrSql) { // 使用As函数,类似 类型断言(type assertion)
fmt.Printf("data not found, %+v\n", err) // 不支持调用栈详情
return
}
if err != nil {
// unknown error
}
}
/* Outputs:
data not found, bar failed: foo failed: sql: no rows in result set
*/
ps:
使用 : %w,如果没有冒号,或 : %w 不位于于格式化字符串的结尾,或冒号与百分号之间没有空格,包装将失效且不报错
Unwrap
func main() {
e := errors.New("原始错误e")
w := fmt.Errorf("Wrap了一个错误: %w", e)
fmt.Println(errors.Unwrap(w))
}
ps:
通过errors.Unwrap(w)后,返回的其实是个e,也就是被嵌套的那个error。 这里需要注意的是,嵌套可以有很多层,我们调用一次errors.Unwrap函数只能返回最外面的一层error,如果想获取更里面的,需要调用多次errors.Unwrap函数。最终如果一个error不是warpping error,那么返回的是nil。
哨兵错误比较
/* 将错误与已知的前哨值(sentinel error)进行比较 */
var ErrNotFound = errors.New("not found")
if err == ErrNotFound {
// do something
}
类型断言比较
/* 错误值可以是满足语言定义的 error 接口的任何类型,可以使用类型断言(type assertion)或类型开关(type switch)来判断错误值 */
type ErrNotFound struct {
Name string
}
func (e *ErrNotFound) Error() string {
Return e.Name + ": not found"
}
if e, ok := err.(*ErrNotFound); ok {
// do something
}
golang.org/x/xerrors
import (
"database/sql"
"fmt"
"golang.org/x/xerrors"
)
func bar() error {
if err := foo(); err != nil { // 需要判断错误是否为空
return xerrors.Errorf("bar failed: %w", foo()) // 引入了一个新的 fmt 格式化动词: %w,使用 Is/As 进行判断
}
return nil
}
func foo() error {
return xerrors.Errorf("foo failed: %w", sql.ErrNoRows) // 引入了一个新的 fmt 格式化动词: %w,使用 Is/As 进行判断
}
func main() {
err := bar()
if xerrors.Is(err, sql.ErrNoRows) { // 使用Is函数,类似 哨兵错误(sentinel error)比较
fmt.Printf("data not found, %v\n", err) // 使用 %v 作为格式化参数,那么错误信息会保持一行, 其中依次包含调用栈的上下文文本
fmt.Printf("%+v\n", err) // 使用 %+v ,则会输出完整的调用栈详情
return
}
if err != nil {
// unknown error
}
}
/* Outputs:data not found, bar failed: foo failed: sql: no rows in result set
bar failed:
main.bar
/usr/four/main.go:12
- foo failed:
main.foo
/usr/four/main.go:18
- sql: no rows in result set
*/
github.com/pkg/errors
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")
}
/*****************/
ps:
Wrap 方法用来包装底层错误,增加上下文文本信息并附加调用栈。 一般用于包装对第三方代码(标准库或第三方库)的调用。
WithMessage 方法仅增加上下文文本信息,不附加调用栈。 如果确定错误已被 Wrap 过或不关心调用栈,可以使用此方法。 注意:不要反复 Wrap ,会导致调用栈重复
Cause 方法用来判断底层错误
/****************/
func main() {
err := bar()
if errors.Cause(err) == sql.ErrNoRows {
fmt.Printf("data not found, %v\n", err) // 使用 %v 作为格式化参数,那么错误信息会保持一行, 其中依次包含调用栈的上下文文本
fmt.Printf("%+v\n", err) // 使用 %+v ,则会输出完整的调用栈详情
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
...*/
// 如果不需要增加额外上下文信息,仅附加调用栈后返回,可以使用 WithStack 方法
func foo() error {
return errors.WithStack(sql.ErrNoRows)
}
ps: 无论是 Wrap, WithMessage 还是 WithStack ,当传入的 err 参数为 nil 时, 都会返回 nil, 这意味着我们在调用此方法之前无需作 nil 判断,保持了代码简洁