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

Pet*_*mel 2 error-handling null pointers interface go

我已经实现了一个自定义错误类型,并且在 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")
    }
}
Run Code Online (Sandbox Code Playgroud)

在操场上也一样:https : //play.golang.org/p/7bqeDw5B5fU

这实际上输出 "err is not nil"

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

编辑:正如 Iain Duncan 所指出的, 可以在此处找到对此的解释

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

func WrapFailCustom(dofail bool) error {
    err := FailCustom(dofail)
    if err == nil {
        return nil
    } else {
        return err
    }
}
Run Code Online (Sandbox Code Playgroud)

这实际上返回“错误为零”:https : //play.golang.org/p/mEKJFyk5zqf

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

icz*_*cza 5

有关背景信息,请参阅隐藏 nil 值,了解 golang 为什么会在这里失败;和Go FAQ:为什么我的 nil 错误值不等于 nil?

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

你的功能:

func WrapFailCustom(dofail bool) error {
    return FailCustom(dofail)
}
Run Code Online (Sandbox Code Playgroud)

WrapFailCustom()有一个返回类型error,而你试图返回FailCustom()函数的结果,其返回类型*CustomErrorerror! 这应该引起一个危险信号!

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

解决方案?

关注根本原因

它真的有道理/它有任何价值,FailCustom()除了返回类型error吗?如果没有,最简单的方法是处理“问题”的根源:

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

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

手动对其进行会计处理 WrapFailCustom()

如果确实需要FailCustom()返回自定义*CustomError类型,则需要在WrapFailCustom(). 我会这样写:

func WrapFailCustom(dofail bool) error {
    if customErr := FailCustom(dofail); customErr != nil {
        return customErr 
    }
    return nil
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

type CustomErr interface {
    Error // Embed error interface
    Code() int
}
Run Code Online (Sandbox Code Playgroud)

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

func (e *CustomError) Code() int { return e.Code }
Run Code Online (Sandbox Code Playgroud)

这有什么用?

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

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

隐式接口值将在FailCustom().

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

与此方法相关,请查看Dave Cheney:不要只检查错误,要优雅地处理它们,尤其是标题为:Assert errors for behavior, not type 的部分