【问题标题】:How to return custom errors without causing problems with nil values如何返回自定义错误而不会导致 nil 值出现问题
【发布时间】:2018-04-11 07:11:10
【问题描述】:

我已经实现了一个自定义错误类型,并且在 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 is not nil”

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

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

为了进一步探讨这个问题,让我们考虑对 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 这样的函数的代码来说似乎真的很不方便,所以我想避免这种情况......

【问题讨论】:

标签: pointers go error-handling interface null


【解决方案1】:

背景见Hiding nil values, understanding why golang fails here;和Go FAQ: Why is my nil error value not equal to nil?

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

你的功能:

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

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
}

然后你所有的问题都消失了。如果您按照“Go way”使用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: Don’t just check errors, handle them gracefully,尤其是标题为:Assert errors for behavior, not type 的部分。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多