Go – How to combine multiple error objects

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

Go - How to combine multiple error objects

问题

假设你想将strconv.Atoi返回的错误与你的命名错误ErrSomething结合起来,以便在不丢失底层错误信息的情况下,使用你的错误"常量"来检查出错的具体原因。你可以使用fmt.Errorf函数来实现这一点。以下是修改后的代码示例:

package lib

import (
    "errors"
    "fmt"
    "strconv"
)

var ErrSomething = errors.New("foobar")

func SomeFunc(str string) (int, error) {
    i, err := strconv.Atoi(str)
    if err != nil {
        return 0, fmt.Errorf("%w: %v", ErrSomething, err)
    }
    
    // 进行其他操作,可能返回其他错误

    return i, nil
}

在这个示例中,我们使用fmt.Errorf函数来将ErrSomethingerr结合起来。%w占位符用于将错误包装为一个Unwrap错误,以便在后续的错误处理中使用errors.Is函数进行检查。通过这种方式,你的用户可以使用errors.Is(err, ErrSomething)来检查错误是否是ErrSomething

英文:

Say I have the following code:

package lib

import (
    "errors"
    "strconv"
)

var ErrSomething = errors.New("foobar")

func SomeFunc(str string) (int, error) {
    i, err := strconv.Atoi(str)
    if err != nil {
        // how to combine ErrSomething with err?
        return 0, fmt.Errorf("%w: %w", ErrSomething, err)
    }
    
    // do other things, potentially return other errors

    return i

}

How do I combine the error returned from strconv.Atoi with my named error ErrSomething. The reason for combining is so that users of my SomeFunc() can check what exactly went wrong using my error "constants" while not losing information about the underlying error.

I have read similar questions but the usual answer is to just do: return 0, fmt.Errorf("foobar: %w", err) but this way my users can't check the error using errors.Is(err, ???)

答案1

得分: 4

你可以通过创建一个实现IsUnwrap方法的错误类型来实现所需的行为,示例如下:

package lib

import (
	"fmt"
	"strconv"
)

type FoobarError struct {
	msg      string
	original error
}

func (err *FoobarError) Error() string {
	return fmt.Sprintf("%s: %s", err.msg, err.original.Error())
}

func (err *FoobarError) Unwrap() error {
	return err.original
}

func (err *FoobarError) Is(target error) bool {
	_, ok := target.(*FoobarError)
	return ok
}

func SomeFunc() error {
	// 这里使用strconv.ErrSyntax作为一个虚拟错误,用于表示可能由strconv.Atoi或其他操作返回的错误。
	err := strconv.ErrSyntax
	return &FoobarError{"foobar", err}
}

用法:

package main

import (
	"errors"
	"fmt"
	"strconv"

	"lib"
)

func main() {
	if err := lib.SomeFunc(); err != nil {
		fmt.Println(err)                                // foobar: invalid syntax
		fmt.Println(errors.Is(err, &lib.FoobarError{})) // true
		fmt.Println(errors.Is(err, strconv.ErrSyntax))  // true
	}
}

你可以在这里了解更多关于这种方法的信息。

附加内容

类似于Go的os.IsExist,你可能有兴趣在你的库中添加一个辅助函数,以便用户更容易地检查错误:

package lib

import (
	"errors"
	// ...
)

// ...

func IsFoobar(err error) bool {
	return errors.Is(err, &FoobarError{})
}

用法:

package main

// ...

func main() {
	err := lib.SomeFunc(); 
    if lib.IsFoobar(err) {
      // ...
	}
}

英文:

You can achieve the desired behavior by creating an error type that implements the Is and Unwrap methods as follows:

package lib

import (
	"fmt"
	"strconv"
)

type FoobarError struct {
	msg      string
	original error
}

func (err *FoobarError) Error() string {
	return fmt.Sprintf("%s: %s", err.msg, err.original.Error())
}

func (err *FoobarError) Unwrap() error {
	return err.original
}

func (err *FoobarError) Is(target error) bool {
	_, ok := target.(*FoobarError)
	return ok
}

func SomeFunc() error {
	// strconv.ErrSyntax is used as a dummy error here for the error
	// that might be returned by strconv.Atoi or any other operation.
	err := strconv.ErrSyntax
	return &FoobarError{"foobar", err}
}

Usage:

package main

import (
	"errors"
	"fmt"
	"strconv"

	"lib"
)

func main() {
	if err := lib.SomeFunc(); err != nil {
		fmt.Println(err)                                // foobar: invalid syntax
		fmt.Println(errors.Is(err, &lib.FoobarError{})) // true
		fmt.Println(errors.Is(err, strconv.ErrSyntax))  // true
	}
}

You can read more about this approach here.

Bonus

Similar to Go's os.IsExist, you may be interested in adding a helper function to your library that makes it easier for the user to check the error:

package lib

import (
  "errors"
  // ...
)

// ...

func IsFoobar(err error) bool {
	return errors.Is(err, &FoobarError{})
}

Usage:

package main

// ...

func main() {
	err := lib.SomeFunc(); 
    if lib.IsFoobar(err) {
      // ...
	}
}

huangapple
  • 本文由 发表于 2022年1月2日 02:31:47
  • 转载请务必保留本文链接:https://go.coder-hub.com/70550975.html
匿名

发表评论

匿名网友

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

确定