Go开发常用第三方库之errors
本文主要介绍 在go开发中 errors 的处理和第三方库 github.com/pkg/errors 的使用。
error interface
官方定义:
// The error built-in interface type is the conventional interface for
// representing an error condition, with the nil value representing no error.
type error interface {
Error() string
}
常用的声明方式
//方式一
err1 := fmt.Errorf("io.EOF")
//方式二
err2 := errors.New("io.EOF")
//方式三: 实现interface
type err3 struct{
}
func (e err3) Error() string {
return "err3"
}
go的错误常见是把错误层层传递,这样可能带来一些不友好的地方:
- 错误判断,经过层次包裹的错误,使用层不好判断真实错误
- 定位错误,error库过于简单,没有提供粗错误堆栈,不好定位问题
错误判断
常见错误的判断方式是:
type base struct{}
func (e base) Error() string {
return "base error"
}
func wrapBase() error {
return fmt.Errorf("wrapBase: %w", base{})
}
func TestErrors(t *testing.T) {
t.Run("==", func(t *testing.T) {
foo := &base{}
bar := &base{}
t.Log(foo)
t.Log(bar)
t.Log(foo == bar)
assert.True(t, foo == bar)
})
t.Run("断言", func(t *testing.T) {
var err error
_, ok := err.(*base)
assert.False(t, ok)
})
t.Run("Is", func(t *testing.T) {
foo := base{}
wrapFoo := wrapBase()
assert.False(t, foo == wrapFoo)
assert.True(t, errors.Is(wrapFoo, foo))
assert.False(t, errors.Is(wrapFoo, &base{}))
})
t.Run("As", func(t *testing.T) {
foo := base{}
wrapFoo := wrapBase()
assert.False(t, foo == wrapFoo)
assert.True(t, errors.As(wrapFoo, &base{}))
})
}
OutPut:
--- PASS: TestErrors (0.00s)
--- PASS: TestErrors/== (0.00s)
--- PASS: TestErrors/断言 (0.00s)
--- PASS: TestErrors/Is (0.00s)
--- PASS: TestErrors/As (0.00s)
- 当没有嵌套错误,可以使用 ==来判断;
- 当有多层嵌套,可以使用 Is() :
- //一个错误被认为匹配一个目标,如果它等于那个目标或如果它实现了一个方法Is(error) bool,使Is(target)返回true。
- // As在err’s chain中找到第一个与target匹配的错误,如果找到,则设置指向错误值并返回true。否则,返回false
定位错误
常用的第三方库
github.com/pkg/errors/errors.go 提供了以下功能:
- WithStack 包装堆栈
- WithMessagef 包装异常
- 等
func TestWithStackCompare(t *testing.T) {
t.Run("fmt.Errorf,无堆栈,不好定位", func(t *testing.T) {
err1 := fmt.Errorf("io.EOF")
fmt.Printf("err1: %+v", err1)
})
t.Run("errors ,无堆栈,不好定位", func(t *testing.T) {
err2 := errors.New("io.EOF")
fmt.Printf("err2: %+v", err2)
})
t.Run("pkgerrors,有堆栈,方便定位", func(t *testing.T) {
err3 := pkgerrors.WithStack(io.EOF)
fmt.Printf("err3: %+v", err3)
})
}
OutPut:
=== RUN TestWithStackCompare
=== RUN TestWithStackCompare/fmt.Errorf,无堆栈,不好定位
err1: io.EOF=== RUN TestWithStackCompare/errors_,无堆栈,不好定位
err2: io.EOF=== RUN TestWithStackCompare/pkgerrors,有堆栈,便以定位
err3: EOF
tkingo.vip/egs/goerrors-demo.TestWithStackCompare.func3
$ workspace/goerrors-demo/errors_test.go:113
testing.tRunner
$ workspace/Go/src/testing/testing.go:1439
runtime.goexit
func TestWithMessagef(t *testing.T) {
tests := []struct {
err error
message string
want string
}{
{io.EOF, "read error", "read error: EOF"},
{pkgerrors.WithMessagef(io.EOF, "read error without format specifier"), "client error", "client error: read error without format specifier: EOF"},
{pkgerrors.WithMessagef(io.EOF, "read error with %d format specifier", 1), "client error", "client error: read error with 1 format specifier: EOF"},
}
for _, tt := range tests {
got := pkgerrors.WithMessagef(tt.err, tt.message).Error()
if got != tt.want {
t.Errorf("WithMessage(%v, %q): got: %q, want %q", tt.err, tt.message, got, tt.want)
}
}
}
总结
pkg errors 应该能够支持错误堆栈、不同的打印格式很好的补充了go errors 的一些短板
参考:
- github.com/pkg/errors