如何返回自定义错误而不会导致nil值出现问题

时间:2018-04-11 07:11:10

标签: pointers go error-handling interface null

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

这确实输出“错误不是零”

我原本期望将* 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等功能的代码来说似乎非常不方便,所以我想避免这种...

1 个答案:

答案 0 :(得分:4)

有关背景信息,请参阅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()函数的结果,其返回类型为*CustomError,与error不同{1}}!这应该引起一面红旗!

将返回什么/如何?将自动创建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类型,应注意如何将其转换为{{ 1}}。)

使用作为接口类型的自定义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甚至可以被取消导出(隐藏)。

与此方法相关,请查看Dave Cheney: Don’t just check errors, handle them gracefully,尤其是标题为“断言行为错误,而不是”的部分。