英文:
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")
}
}
到目前为止,一切都好。不幸的是,为了实现WrapA
和WrapB
,我需要大量的代码:
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
结构体,并让WrapA
和WrapB
继承自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
如果我理解问题正确的话,你确实可以简化一些东西:
- 为了方便和更好的性能,将
a
和b
定义为包级别的error
变量。 - 除非你需要以编程方式访问只能在包装错误的上下文中访问的值,否则你很可能不需要声明那些自定义的
WrapA
和WrapB
错误类型。相反,你可以简单地使用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
andb
as package-levelerror
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
andWrapB
error types. Instead, you can simply use the%w
verb in conjunction withfmt.Errorf
to produce a new error value that wraps the lower-level error. - You can then use
errors.Is
within a taglessswitch
to inspect the cause of the higher-level error returned by yourdoStuff
function.
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论