本文主要介绍 在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