如何对包装的错误类进行子类型化?

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

Go - How can I subtype a wrapped error class?

问题

我正在包装错误(以添加上下文),然后区分两种错误。这是我目前用于测试的场景。(函数是否正确识别错误?)我的问题是如何减少冗余。

我有两个创建不同错误的函数:

func a() error {
	return errors.New("a")
}

func b() error {
	return errors.New("b")
}

它们都由一个传播错误的第三个函数调用。

func doStuff() error {
	err := a()
	if err != nil {
		return WrapA{err}
	}
	err = b()
	if err != nil {
		return WrapB{err}
	}
	return nil
}

在我的主函数中,我区分这两种错误。

func main() {
	fmt.Println("Hello, playground")
	err := doStuff()
	
	switch err.(type) {
		case WrapA:
			fmt.Println("error from doing a")
		case WrapB: 
			fmt.Println("error from doing b")
		case nil:
			fmt.Println("nil")
		default:
			fmt.Println("unknown")
	}
}

到目前为止,一切都好。不幸的是,为了实现WrapAWrapB,我需要大量的代码:

type WrapA struct {
	wrappedError error
}

func (e WrapA) Error() string {
	return e.wrappedError.Error()
}

func (e WrapA) Unwrap() error {
	return e.wrappedError
}

type WrapB struct {
	wrappedError error
}

func (e WrapB) Error() string {
	return e.wrappedError.Error()
}

func (e WrapB) Unwrap() error {
	return e.wrappedError
}

在其他语言中,我会创建一个单独的Wrap结构体,并让WrapAWrapB继承自Wrap。但是我不知道如何在Go中实现这一点。

有没有办法减少这些冗余代码的想法?

Go Playground链接:https://play.golang.org/p/ApzHC_miNyV

编辑:
在看到jub0bs的答案后,我想澄清一下:
a()b()都是我无法控制的回调函数。它们可能返回各种错误。这就是我包装它们的原因。

英文:

I am wrapping errors (to add context) and am afterwards distinguishing between two errors. This is a scenario that I am currently using for tests. (Did the function recognise the error correctly?) My question is how I can reduce the verbosity.

I have two functions that create different errors:

func a() error {
	return errors.New("a")
}

func b() error {
	return errors.New("b")
}

They are both called by a third function that propagates the erorr.

func doStuff() error {
	err := a()
	if err != nil {
		return WrapA{err}
	}
	err = b()
	if err != nil {
		return WrapB{err}
	}
	return nil
}

In my main function, I distinguish between both errors.

func main() {
	fmt.Println("Hello, playground")
	err := doStuff()
	
	switch err.(type) {
		case WrapA:
			fmt.Println("error from doing a")
		case WrapB: 
			fmt.Println("error from doing b")
		case nil:
			fmt.Println("nil")
		default:
			fmt.Println("unknown")
	}
}

So far, so good. Unfortunately, to implement WrapA and WrapB, I need a lot of code:

type WrapA struct {
	wrappedError error
}

func (e WrapA) Error() string {
	return e.wrappedError.Error()
}

func (e WrapA) Unwrap() error {
	return e.wrappedError
}

type WrapB struct {
	wrappedError error
}

func (e WrapB) Error() string {
	return e.wrappedError.Error()
}

func (e WrapB) Unwrap() error {
	return e.wrappedError
}

In other languages, I would create a single Wrap struct and let WrapA and WrapB inherit from Wrap. But I don't see a way to do this in Go.

Any ideas on how to reduce the clutter?

Go Playground https://play.golang.org/p/ApzHC_miNyV

EDIT:
After seeing jub0bs answer, I want to clarify:
Both a() and b() are callbacks I have no control over. They may return various errors. This is the reason why I wrap them.

答案1

得分: 5

如果我理解问题正确的话,你确实可以简化一些东西:

  • 为了方便和更好的性能,将ab定义为包级别的error变量。
  • 除非你需要以编程方式访问只能在包装错误的上下文中访问的值,否则你很可能不需要声明那些自定义的WrapAWrapB错误类型。相反,你可以简单地使用fmt.Errorf中的%w动词来生成一个包装了较低级别错误的新错误值。
  • 然后,你可以在没有标签的switch语句中使用errors.Is来检查doStuff函数返回的更高级别错误的原因。

以下是修改后的代码示例:

package main

import (
	"errors"
	"fmt"
)

var (
	a = errors.New("a")
	b = errors.New("b")
)

func doStuff() error {
	err := a
	if err != nil {
		return fmt.Errorf("%w", err)
	}
	err = b
	if err != nil {
		return fmt.Errorf("%w", err)
	}
	return nil
}

func main() {
	fmt.Println("Hello, playground")
	switch err := doStuff(); {
	case errors.Is(err, a):
		fmt.Println("error from doing a")
	case errors.Is(err, b):
		fmt.Println("error from doing b")
	case err == nil:
		fmt.Println("nil")
	default:
		fmt.Println("unknown")
	}
}

你可以在Playground上运行这段代码。

英文:

If I understand the problem correctly, you can indeed simplify things:

  • Define a and b as package-level error variables for ease and better performance.
  • Unless you require programmatic access to values only accessible in the context of the error that you're wrapping, you most likely don't need to declare those custom WrapA and WrapB error types. Instead, you can simply use the %w verb in conjunction with fmt.Errorf to produce a new error value that wraps the lower-level error.
  • You can then use errors.Is within a tagless switch to inspect the cause of the higher-level error returned by your doStuff function.

(Playground)

package main

import (
	"errors"
	"fmt"
)

var (
	a = errors.New("a")
	b = errors.New("b")
)

func doStuff() error {
	err := a
	if err != nil {
		return fmt.Errorf("%w", err)
	}
	err = b
	if err != nil {
		return fmt.Errorf("%w", err)
	}
	return nil
}

func main() {
	fmt.Println("Hello, playground")
	switch err := doStuff(); {
	case errors.Is(err, a):
		fmt.Println("error from doing a")
	case errors.Is(err, b):
		fmt.Println("error from doing b")
	case err == nil:
		fmt.Println("nil")
	default:
		fmt.Println("unknown")
	}
}

答案2

得分: 2

添加一个结构化错误版本,其中包含一个类型为Wrap的组合以及其他更具体的错误类型;

package main

import (
	"errors"
	"fmt"
)

func a() error {
	return errors.New("在a中发生了更具体的错误")
}

func b() error {
	return errors.New("在b中发生了更具体的错误")
}

func doStuff() error {
	err := a()
	if err != nil {
		return ErrA{
			Wrap:        Wrap{err: err},
			SpecficProp: "任何内容",
		}
	}
	err = b()
	if err != nil {
		return ErrB{
			Wrap:         Wrap{err: err},
			SpecficProp2: "其他内容",
		}
	}
	return nil
}

func main() {
	fmt.Println("Hello, playground")
	err := doStuff()

	if target := (ErrA{}); errors.As(err, &target) {
		fmt.Printf("%v\n", target)
	} else if target := (ErrB{}); errors.As(err, &target) {
		fmt.Printf("%v\n", target)
	} else if err != nil {
		fmt.Println("未知错误")
	} else {
		fmt.Println("nil")
	}
}

type Wrap struct {
	err error
}

func (e Wrap) Error() string {
	return e.err.Error()
}

func (e Wrap) Unwrap() error {
	return e.err
}

type ErrA struct {
	Wrap
	SpecficProp interface{}
}

func (e ErrA) Error() string {
	return fmt.Sprintf("发生了A类型的错误,具体属性为%#v,加上%T", e.SpecficProp, e.Unwrap())
}

type ErrB struct {
	Wrap
	SpecficProp2 interface{}
}

func (e ErrB) Error() string {
	return fmt.Sprintf("发生了B类型的错误,具体属性为%#v,加上%T", e.SpecficProp2, e.Unwrap())
}
英文:

Adding a structured error version which composes a type Wrap along various more specific error types;

package main
import (
"errors"
"fmt"
)
func a() error {
return errors.New("something more specific broke in a")
}
func b() error {
return errors.New("something more specific broke in b")
}
func doStuff() error {
err := a()
if err != nil {
return ErrA{
Wrap:        Wrap{err: err},
SpecficProp: "whatever",
}
}
err = b()
if err != nil {
return ErrB{
Wrap:         Wrap{err: err},
SpecficProp2: "whatever else",
}
}
return nil
}
func main() {
fmt.Println("Hello, playground")
err := doStuff()
if target := (ErrA{}); errors.As(err, &target) {
fmt.Printf("%v\n", target)
} else if target := (ErrB{}); errors.As(err, &target) {
fmt.Printf("%v\n", target)
} else if err != nil {
fmt.Println("unknown")
} else {
fmt.Println("nil")
}
}
type Wrap struct {
err error
}
func (e Wrap) Error() string {
return e.err.Error()
}
func (e Wrap) Unwrap() error {
return e.err
}
type ErrA struct {
Wrap
SpecficProp interface{}
}
func (e ErrA) Error() string {
return fmt.Sprintf("got error of kind A with %#v, plus %T", e.SpecficProp, e.Unwrap())
}
type ErrB struct {
Wrap
SpecficProp2 interface{}
}
func (e ErrB) Error() string {
return fmt.Sprintf("got error of kind B with %#v, plus %T", e.SpecficProp2, e.Unwrap())
}

答案3

得分: 0

如果可以的话,尽量使用常量错误。然后你可以根据错误本身进行切换。

英文:

Use constant errors if you can. Then you could switch on the error itself.

huangapple
  • 本文由 发表于 2021年9月10日 00:05:12
  • 转载请务必保留本文链接:https://go.coder-hub.com/69121476.html
匿名

发表评论

匿名网友

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

确定