在Go中检查错误是否被包装的优雅方式

huangapple go评论92阅读模式
英文:

Elegant way to check error is wrapped in Go

问题

我们有一个函数,其中一个错误被errors.Wrap()包装,其他错误没有被包装。

var ErrTest1 = errors.New("error test 1")
var ErrTest2 = errors.New("error test 2")
var ErrRPC = errors.New("error rpc")

func rpcCall() error {
	return ErrRPC
}

func testErrWrap(a int) error {
	if a == 1 {
		return ErrTest1
	} else if a == 2 {
		return ErrTest2
	} else {
		err := rpcCall()
		if err != nil {
			return errors.Wrap(ErrRPC, "call rpc err")
		}
	}
	return nil
}

我们有两种解决方案,一种是

	err := testErrWrap(3)

	if errors.Unwrap(err) != nil {
		fmt.Println(errors.Unwrap(err))
	}

另一种是

	err := testErrWrap(3)

	if !errors.Is(err, ErrTest2) && !errors.Is(err, ErrTest1) {
		tErr := errors.Unwrap(err)
		fmt.Println(tErr)
	}

我们想知道在Go中区分错误是否被包装的优雅方式是什么?

英文:

We have one function that returns errors one of them is wrapped by errors.Wrap(), the others are not.

var ErrTest1 = errors.New("error test 1")
var ErrTest2 = errors.New("error test 2")
var ErrRPC = errors.New("error rpc")

func rpcCall() error {
	return ErrRPC
}

func testErrWrap(a int) error {
	if a == 1 {
		return ErrTest1
	} else if a == 2 {
		return ErrTest2
	} else {
		err := rpcCall()
		if err != nil {
			return errors.Wrap(ErrRPC, "call rpc err")
		}
	}
	return nil
}

We have two solutions, one is

	err := testErrWrap(3)

	if errors.Unwrap(err) != nil {
		fmt.Println(errors.Unwrap(err))
	}

the other is

	err := testErrWrap(3)

	if !errors.Is(err, ErrTest2) && !errors.Is(err, ErrTest1) {
		tErr := errors.Unwrap(err)
		fmt.Println(tErr)
	}

We want to know the elegant way to distinguish errors are wrapped or not in Go?

答案1

得分: 3

在大多数情况下,我们希望确定是否出现了特定类型的错误,以便执行一些特殊逻辑来处理它。我认为有比查看错误是否被包装的更好的方法来实现这一点。

在这种情况下,我建议使用自定义错误类型和使用errors.Iserrors.As

首先,让我们创建一些具有自定义错误类型的代码:

type ErrRPC struct { 
   retryPort int
}

func (e ErrRPC) Error() string {
	return "Oh no, an rpc error!"
}

func testErrWrap(a int) error {
	switch a {
	case 1:
		return errors.New("errors test 1")
	case 2:
		return errors.New("errors test 1")
	default:
		err := rpcCall(9000)
		return fmt.Errorf("errors test: %w", err) // Wraps the ErrRPC
	}
}

func rpcCall(port int) error {
	return ErrRPC{retryPort: port + 1}
}

如果我们只对获取特定错误类型时的不同代码路径感兴趣,我会选择使用errors.Is

func main() {
	for i := 1; i <= 3; i++ {
		if err := testErrWrap(i); err != nil {
			if errors.Is(err, ErrRPC{}) {
				println("rpc error")
			} else {
				println("regular error")
			}
		}
	}
}

如果我们需要使用错误值的某个属性,errors.As会很方便。

func main() {
	for i := 1; i <= 3; i++ {
		if err := testErrWrap(i); err != nil {
            var rpcErr ErrRPC
			if errors.As(err, &rpcErr) {
				fmt.Printf("rpc error, retrying on port: %d", rpcErr.retryPort)
			} else {
				println("regular error")
			}
		}
	}
}
英文:

In most cases we're looking to find out if we got a specific type of error in order to do some special logic to handle it. I'd argue there are better ways to do this than looking at whether the error was wrapped or not.

In this case I'd propose using custom error types and using errors.Is or errors.As

First let's create some code that has a custom error type:

type ErrRPC struct { 
   retryPort int
}

func (e ErrRPC) Error() string {
	return &quot;Oh no, an rpc error!&quot;
}

func testErrWrap(a int) error {
	switch a {
	case 1:
		return errors.New(&quot;errors test 1&quot;)
	case 2:
		return errors.New(&quot;errors test 1&quot;)
	default:
		err := rpcCall(9000)
		return fmt.Errorf(&quot;errors test: %w&quot;, err) // Wraps the ErrRPC
	}
}

func rpcCall(port int) error {
	return ErrRPC{retryPort: port + 1}
}

If we're only interested in going down a different code path when we get a specific error type I'd go with errors.Is

func main() {
	for i := 1; i &lt;= 3; i++ {
		if err := testErrWrap(i); err != nil {
			if errors.Is(err, ErrRPC{}) {
				println(&quot;rpc error&quot;)
			} else {
				println(&quot;regular error&quot;)
			}
		}
	}
}

If we need to use a property of the error value for something, errors.As comes in handy.

func main() {
	for i := 1; i &lt;= 3; i++ {
		if err := testErrWrap(i); err != nil {
            var rpcErr ErrRPC
			if errors.As(err, &amp;rpcErr) {
				fmt.Printf(&quot;rpc error, retrying on port: %d&quot;, rpcErr.retryPort)
			} else {
				println(&quot;regular error&quot;)
			}
		}
	}
}

答案2

得分: 0

另一种方法来自puellanivis

还要考虑测试行为:

err := testErrWrap(3)

var wrappedErr interface { Unwrap() error }
if errors.As(err, &wrappedErr) {
	fmt.Println(errors.Unwrap(err))
}

但需要注意的是,errors.Wrap(…)会对错误进行双重封装:一个是WithMessage,一个是WithStack。因此,在对errors.Wrap(ErrRPC, "call rpc err")返回的错误使用errors.Unwrap(err)时,将无法获取到ErrRPC。

英文:

Another method from puellanivis

> Also consider testing for behavior:

err := testErrWrap(3)

var wrappedErr interface { Unwrap() error }
if errors.As(err, &amp;wrappedErr) {
	fmt.Println(errors.Unwrap(err))
}

> But also of note, errors.Wrap(…) double wraps your error: one WithMessage and one WithStack. So using errors.Unwrap(err) on the error from errors.Wrap(ErrRPC, &quot;call rpc err&quot;) will not give you ErrRPC.

huangapple
  • 本文由 发表于 2021年7月28日 17:33:57
  • 转载请务必保留本文链接:https://go.coder-hub.com/68557870.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定