【问题标题】:Return nil or custom error in Go在 Go 中返回 nil 或自定义错误
【发布时间】:2017-09-03 10:32:13
【问题描述】:

在 Go 中使用自定义 Error 类型时(使用额外字段来捕获一些详细信息),当尝试将 nil 作为此类型的值返回时,我会收到类似 cannot convert nil to type DetailedErrorcannot use nil as type DetailedError in return argument 之类的编译错误,从代码看起来大多是这样的:

type DetailedError struct {
    x, y int
}

func (e DetailedError) Error() string {
    return fmt.Sprintf("Error occured at (%s,%s)", e.x, e.y)
}

func foo(answer, x, y int) (int, DetailedError) {
    if answer == 42 {
        return 100, nil  //!! cannot use nil as type DetailedError in return argument
    }
    return 0, DetailedError{x: x, y: y}
}

(完整的sn-p:https://play.golang.org/p/4i6bmAIbRg

解决这个问题的惯用方法是什么?(或任何可行的方法......)

我实际上需要额外的错误字段,因为我有详细的错误消息,这些错误消息是由复杂逻辑从更简单的逻辑等构建的,如果我只是回到“字符串错误”,我基本上必须解析 将这些字符串分解成碎片并根据它们发生逻辑等等,这看起来真的很难看(我的意思是,为什么要序列化到字符串信息,你知道以后需要反序列化......)

【问题讨论】:

    标签: go error-handling compiler-errors null


    【解决方案1】:

    不要使用DetailedError作为返回类型,始终使用error

    func foo(answer, x, y int) (int, error) {
        if answer == 42 {
            return 100, nil  //!! cannot use nil as type DetailedError in return argument
        }
        return 0, DetailedError{x: x, y: y}
    }
    

    您的DetailedError 类型满足error 接口这一事实足以完成这项工作。然后,在您的调用者中,如果您关心额外的字段,请使用类型断言:

    value, err := foo(...)
    if err != nil {
        if detailedErr, ok := err.(DetailedError); ok {
            // Do something with the detailed error values
        } else {
            // It's some other error type, behave accordingly
        }
    }
    

    不回DetailedError的原因:

    现在看起来可能无关紧要,但将来您的代码可能会扩展以包含额外的错误检查:

    func foo(answer, x, y int) (int, error) {
        cache, err := fetchFromCache(answer, x, y)
        if err != nil {
            return 0, fmt.Errorf("Failed to read cache: %s", err)
        }
        // ... 
    }
    

    其他错误类型不会是DetailedError 类型,因此您必须返回error

    此外,您的方法可能会被不知道或不关心 DetailedError 类型的其他调用者使用:

    func fooWrapper(answer, x, y int) (int, error) {
        // Do something before calling foo
        result, err := foo(answer, x, y)
        if err != nil {
            return 0, err
        }
        // Do something after calling foo
        return result, nil
    }
    

    期望函数的每个调用者都理解自定义错误类型是不合理的——这正是接口,特别是 error 接口存在于 Go 中的原因。

    利用这一点,不要规避它。

    即使您的代码从不更改,为每个函数或用例设置新的自定义错误类型也是不可持续的,并且会使您的代码不可读且无法推理。

    【讨论】:

    • 谢谢,这真的很彻底!虽然关于“为每个函数或用例设置一个新的自定义错误类型是不可持续的,并且会使您的代码不可读且无法推理”...不知道,这就是我最终要做的事情在使用 exceptions 的语言编写代码时......虽然我不会说生成的代码接近“易于推理”,但我同意......好吧,我会尝试将“Go way”作为“在罗马时”...... :)
    • @NeuronQ:在像 Go 这样的现代语言中,异常是一种反模式。他们正在解决一个允许多个返回值的现代编程语言中不再存在的问题,因此在现代语言中使用像这样的过时模式会给你带来痛苦:)
    • "例外是一种反模式。"有任何支持这个 pov 的重要来源的链接吗?在我工作的最后一个代码库之后,我的“直觉”告诉我也同意它,但还有另一种解释是我刚刚使用“白痴编写的基于异常的代码”......
    • @NeuronQ:创建异常是为了在只允许单个返回值的语言(如 C)中传递带外错误信息。在这样的语言中,它几乎是错误处理的唯一选择。在像 Go 这样的语言中,可能有多个返回值,没有理由对标准错误使用异常,所以我认为这使它成为一种反模式。
    • @NeuronQ 是的,几种语言(Python、Perl 等)允许多个返回值也使用异常。这是这些语言最丑陋的特征之一,IMO。
    【解决方案2】:

    解决问题的惯用方法是返回错误接口。

    如果你确实需要一个不属于错误接口的函数,你应该创建一个扩展错误接口的新接口。

    type DetailedErrorInterface interface {
      error
      GetX() int //need to implement
      GetY() int //need to implement
    }
    

    然后,你必须改变你的函数来返回这个接口:

    func foo(answer, x, y int) (int, DetailedErrorInterface) {
      if answer == 42 {
        return 100, nil
      }
      return 0, DetailedError{x: x, y: y}
    }
    

    【讨论】:

    • 不,这根本不是惯用的。惯用的方法是返回一个error,它已经是一个接口。
    • 所以您更喜欢强制转换自定义错误,而不是安全地将其作为输出返回?有什么理由支持这个决定吗?
    • 同样,Go 中没有强制转换
    • 除此之外,您应该总是,总是返回error。如果您需要访问该错误的自定义方面,是的,您应该进行类型断言。
    • 如果我们可以断言我们想要的任何类型,为什么我们有一个输出签名?这没有意义。另外,“总是,总是”返回错误是否有任何合乎逻辑的理由?
    【解决方案3】:

    DetailedError struct 零值不是nil 而是DetailedError{}。您可以返回error 接口而不是DetailedError

    func foo(answer, x, y int) (int, error) {
    

    或使用指针

    func foo(answer, x, y int) (int, *DetailedError) {
    ...
    //and
    func (e *DetailedError) Error() string {
    

    【讨论】:

    • 谢谢!然后是指针,因为使用error 迫使我添加一个丑陋的err := err.(DetailedError) 类型断言,这样我就可以访问像err.x 这样的额外字段...
    • @NeuronQ 惯用的方法是使用 error 作为返回类型,而不是自定义错误类型 (DetailedError)。当你对错误的细节感兴趣时,你会键入断言,这也是 Go 的惯用语,不管它是否丑陋都是主观的。
    • IMO,将非error 作为错误返回比类型断言丑得多。
    • @NeuronQ 我在上面的答案中添加了解释。如果您需要进一步解释,请告诉我。
    • 不返回 *DetailedError 的另一个原因是某些调用者可能会试图将其视为一般错误并将其返回给调用者。但是一个 nil *DetailedError 不是一个 nil 错误,因为它包含一个已知类型,所以调用者的调用者会认为有一个错误,即使没有。然后当他们尝试记录消息时,他们会感到恐慌,因为该值仍然为零。请参阅play.golang.org/p/m4S7GDfb2Y 您应该只使用错误类型并让智能调用者检查并转换它。键入的错误在 go 中只是不惯用的。
    猜你喜欢
    • 2013-07-26
    • 2018-01-06
    • 1970-01-01
    • 1970-01-01
    • 2016-10-05
    • 2018-11-14
    • 1970-01-01
    • 1970-01-01
    • 2021-12-04
    相关资源
    最近更新 更多