如何在不导致 nil 值问题的情况下返回自定义错误



我已经实现了一个自定义错误类型,并且在 nil 值方面确实得到了奇怪的行为。当我确实将自定义错误作为标准错误接口传递时,即使自定义错误返回为 nil,这也永远不会被识别为 nil。

看看这个小测试程序:

package main
import (
"fmt"
"strconv"
)
type CustomError struct {
Code int
}
func (e *CustomError) Error() string {
return strconv.Itoa(e.Code)
}
func FailCustom(dofail bool) *CustomError {
if dofail {
return &CustomError{Code: 42}
} else {
return nil
}
}
func WrapFailCustom(dofail bool) error {
return FailCustom(dofail)
}
func main() {
err := WrapFailCustom(false)
if err == nil {
fmt.Println("err is nil")
} else {
fmt.Println("err is not nil")
}
}

操场上相同:https://play.golang.org/p/7bqeDw5B5fU

这实际上确实输出"err 不是零"。

我本来希望 *CustomError 类型的 nil 值被隐式转换为类型错误的 nil 值。谁能向我解释一下,为什么不是这种情况以及如何正确传播自定义错误类型的 nil 值?

编辑:正如伊恩·邓肯(Iain Duncan)指出的那样,可以在此处找到对此的解释

为了进一步探讨这个问题,让我们考虑对 WrapFailCustom 进行以下修改:

func WrapFailCustom(dofail bool) error {
err := FailCustom(dofail)
if err == nil {
return nil
} else {
return err
}
}

这实际上确实返回"err is nil":https://play.golang.org/p/mEKJFyk5zqf

依靠它作为解决方案,我确实感觉非常糟糕,因为在使用确实吐出我的自定义错误的函数时很容易忘记它。是否有更好的方法来制作自定义错误,以防止这种"歧义"发生?一直只使用基本错误类型的明显解决方案,对于使用 WrapFailCustom 等函数的代码来说似乎真的很不方便,所以我想避免这种情况......

有关背景信息,请参阅隐藏 nil 值,了解 golang 在此处失败的原因;和 Go 常见问题解答:为什么我的 nil 错误值不等于 nil?

Go 要求您明确类型和转换(例如,您不能将int32类型的值添加到int类型的值),但接口转换和自动接口值创建是此规则的例外。每当需要接口类型的值时,您可以使用其类型实现(满足)接口类型的任何值,并且将自动为您创建接口值。

您的职能:

func WrapFailCustom(dofail bool) error {
return FailCustom(dofail)
}

WrapFailCustom()有一个返回类型error,你尝试返回FailCustom()函数的结果,其返回类型是*CustomError这与error不同!这应该引起危险信号!

什么/如何退货?将自动创建error类型的接口值!*CustomError实现了error,所以一切都很好,但正如你所经历的,如果指针nil,这种隐式值包装不会导致接口值被nil,而是一个非nil接口值包装nil和类型*CustomError

解决方案是什么?

照顾根本原因

真的有道理吗/FailCustom()拥有error以外的返回类型是否有任何价值?如果没有,最简单的方法是在"问题"的来源处处理它:

func FailCustom(dofail bool) error {
if dofail {
return &CustomError{Code: 42}
}
return nil
}

然后你所有的问题都消失了。如果您按照"Go 方式"使用error类型返回错误,这完全足够且令人满意。您甚至不再需要WrapFailCustom()功能。

WrapFailCustom()中手动核算它

如果您确实需要FailCustom()返回自定义*CustomError类型,则需要在WrapFailCustom()中"手动"考虑它。我会这样写:

func WrapFailCustom(dofail bool) error {
if customErr := FailCustom(dofail); customErr != nil {
return customErr 
}
return nil
}

(请注意,我故意使用了不同的customErr名称而不是err,表示它不是error类型,并且应该注意如何将其转换为error

使用作为接口类型的自定义error类型

如果您想/需要使用自定义错误类型,另一种好方法是创建一个描述其包含的"额外"功能的接口类型:

type CustomErr interface {
Error // Embed error interface
Code() int
}

然后我们还需要实现这个Code()方法:

func (e *CustomError) Code() int { return e.Code }

这有什么用?

根本原因将通过返回此接口类型的值(而不是指针)来解决:

func FailCustom(dofail bool) CustomErr {
if dofail {
return &CustomError{Code: 42}
}
return nil
}

隐式接口值将在FailCustom()中创建。

此外,WrapFailCustom()变得不必要/无用。FailCustom()返回一个既是error的值,您可以使用其Code()方法从中获取Code。返回值是一个接口值,它是一个error,你可以在需要error值的地方使用它。混凝土类型CustomError甚至可以不出口(隐藏)。

与这种方法相关,请查看Dave Cheney:不要只是检查错误,要优雅地处理它们,尤其是标题为:断言行为错误而不是类型的部分。

相关内容

  • 没有找到相关文章