从构造函数返回自定义错误类型的惯用方法是什么?[关闭]

What is idiomatic way to return custom error type from constructor? [closed]

提问人:tte 提问时间:11/17/2023 更新时间:11/18/2023 访问量:45

问:


想改进这个问题吗?更新问题,以便可以通过编辑这篇文章用事实和引文来回答。

2天前关闭。

据我所知,在golang源代码中,除了包本身之外,没有错误构造函数的示例。基本上,作者返回手动构造的类型。errors

例如,我们有带有自定义错误结构的包netOpError

type OpError struct {
    // Op is the operation which caused the error, such as
    // "read" or "write".
    Op string

    ...
}

func (e *OpError) Error() string {
    ...
}

他们通过直接声明来使用它们,没有任何构造函数

func (c *conn) SetDeadline(t time.Time) error {
    if !c.ok() {
        return syscall.EINVAL
    }
    if err := c.fd.SetDeadline(t); err != nil {
        return &OpError{Op: "set", Net: c.fd.net, Source: nil, Addr: c.fd.laddr, Err: err}
    }
    return nil
}

func (c *conn) File() (f *os.File, err error) {
    f, err = c.fd.dup()
    if err != nil {
        err = &OpError{Op: "file", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err}
    }
    return
}

我想知道,根据我应该返回的类型编写自定义错误构造函数的惯用方法是什么?我应该返回内置接口错误还是我的自定义类型的实例?

也许有我们的自定义域错误类型,其中包含一些域代码和抽象详细信息

type DomainErrorCode int

type DomainError struct {
    err error
    Code DomainErrorCode
    Details []byte
}

func (e *DomainError) Error() string {
    return e.err.Error()
}

构造函数可以是

// A
func NewDomainError(err error, code DomainErrorCode, details Details) error {
    return &DomainError{err: err, Code: code, Details: details}
}

// B
func NewDomainError(err error, code DomainErrorCode, details Details) *DomainError {
    return &DomainError{err: err, Code: code, Details: details}
}

选项非常灵活,允许您构造自定义错误和到达属性,如 或 。 Option 更加封装,如果您需要访问自定义属性,则需要某种断言。BCodeDetailsA

然后我看到了 Generality 的官方指南

如果某个类型仅用于实现接口,并且永远不会有超出该接口的导出方法,则无需导出类型本身。仅导出接口可以清楚地表明,除了接口中描述的内容之外,该值没有其他有趣的行为。它还避免了在通用方法的每个实例上重复文档的需要。

那么在这个例子中编写构造函数的惯用方法是什么?似乎是选项,因为除了内置之外,我没有任何方法。但这限制了对重要自定义属性的访问,并且如果需要字段,则类型断言将花费额外的精力。对我来说,选择似乎更合理Ainterface errorB

评论

1赞 JimB 11/17/2023
如果可能 go.dev/doc/faq#nil_error,我总是会返回接口类型。对于显式构造函数来说,情况并没有那么糟糕,但是需要类型断言的点不是在构造函数之后,而是在链的下游,在那里它被作为实际的.error

答:

1赞 Burak Serdar 11/17/2023 #1

不要返回键入的错误。总是返回.这有几个原因。其中两个是:error

  1. 返回的错误可能包装到其他错误类型中,也可能包装其他错误类型。因此,在堆栈的较高位置,您应该处理 ,并将其转换为您期望使用包中的工具的具体错误类型。这样,您可以处理已知的错误,并检测您不知道的错误。errorerrors
  2. 更重要的是,如果使用具体类型定义错误变量,则从该变量派生的接口永远不会为 nil,即使具体类型本身为 nil。那是:error
func f() *MyError {return nil}

var err error
err=f()
if err==nil {  // This is never nil
   ...
}

这是因为接口同时包含类型和值,如果类型为非 nil,则接口值本身不能为 nil,即使值为 nil。

0赞 Phelipe Wener 11/18/2023 #2

Golang 建议直接使用错误类型。例如,除非您想将其用作 API 的结果。

var (
    ErrFatalViolation = errors.New("fatal violation")
)

所以你可以退回它:

    return errors.Join(
                ports.ErrFatalViolation,
                errors.New("id is nil"),
            )

并像这样使用它:

   if errors.Is(err, ports.ErrFatalViolation)