【问题标题】:How to compare Go errors如何比较 Go 错误
【发布时间】:2016-08-24 10:46:08
【问题描述】:

我有一个错误值,在控制台上打印时给我Token is expired

如何将它与特定的错误值进行比较?我试过了,但没有用:

if err == errors.New("Token is expired") {
      log.Printf("Unauthorised: %s\n", err)
}

【问题讨论】:

  • 我会避免使用公认的方法。查看 Dαve Cheney 关于错误处理的演示文稿youtube.com/watch?v=lsBF58Q-DnY。我一定会回答你的问题。

标签: go error-handling compare


【解决方案1】:

声明错误并将其与“==”(如err == myPkg.ErrTokenExpired)进行比较不再是 Go 1.13(2019 年第三季度)的最佳实践

release notes 提到:

Go 1.13 包含对错误包装的支持,这在Error Values proposal 中首次提出并在associated issue 上进行了讨论。

一个错误e 可以通过提供一个返回wUnwrap 方法来包装另一个错误w
ew 都可用于程序,允许 ew 提供额外的上下文或重新解释它,同时仍然允许程序根据 w 做出决策。

为了支持包装,fmt.Errorf 现在有一个用于创建包装错误的 %w 动词,errors 包中的三个新函数(errors.Unwraperrors.Iserrors.As)简化了展开和检查包装错误。

Error Value FAQ 解释道:

您需要做好准备,您收到的错误可能会被包装。

如果您当前使用== 比较错误,请改用errors.Is
示例:

if err == io.ErrUnexpectedEOF

变成

if errors.Is(err, io.ErrUnexpectedEOF)
  • 如果err != nil 不需要更改,则检查表单。
  • 无需更改与 io.EOF 的比较,因为永远不应包装 io.EOF

如果您使用类型断言类型开关检查错误类型,请改用errors.As。示例:

if e, ok := err.(*os.PathError); ok

变成

var e *os.PathError
if errors.As(err, &e)

也可以使用此模式来检查错误是否实现了接口。 (当指向接口的指针合适时,这是极少数情况之一。)

将类型开关重写为if-elses的序列。

【讨论】:

  • 你真的配得上100万的声望!
【解决方案2】:

不鼓励通过字符串比较错误。相反,您应该按值比较错误。

package main

import "errors"

var NotFound = errors.New("not found")

func main() {
    if err := doSomething(); errors.Is(err, NotFound) {
        println(err)
    }
}

func doSomething() error {
    return NotFound
}

如果您是库作者并希望导出错误,那么它特别有用,以便用户可以对不同类型的错误采取不同的行动。标准库也可以。

这种方法的问题是任何人都可以更改导出的值,因为 Go 不支持不可变值。不过,没有什么能阻止您将字符串用作错误并将其设为const

package main

type CustomError string

func (ce CustomError) Error() string {
    return string(ce)
}

const NotFound CustomError = "not found"

func main() {
    if err := doSomething(); errors.Is(err, NotFound) {
        println(err)
    }
}

func doSomething() error {
    return NotFound
}

这是更冗长但更安全的方法。

【讨论】:

    【解决方案3】:

    您应该首先考虑按值比较错误,如其他解决方案中所述:

    if errors.Is(err1, err2) {
      // do sth
    }
    

    但是在某些情况下,从函数返回的错误有点复杂,例如一个错误被多次包装,并在每个函数调用中添加一个上下文,如fmt.Errorf("some context: %w", err),您可能只是想比较两个错误的错误消息。在这种情况下,您可以这样做:

    // SameErrorMessage checks whether two errors have the same messages.
    func SameErrorMessage(err, target error) bool {
        if target == nil || err == nil {
            return err == target
        }
        return err.Error() == target.Error()
    }
    
    func main() {
      ...
      if SameErrorMessage(err1, err2) {
         // do sth
      }
    
    }
    

    请注意,如果您只是使用

    if err1.Error() == err2.Error() {
      // do sth
    }
    

    如果err1err2 中的任何一个为nil,您可能会遇到零指针取消引用运行时错误。

    【讨论】:

      【解决方案4】:

      此答案适用于 Go 1.12 及更早版本。

      在库中定义错误值

      package fruits
      
      var NoMorePumpkins = errors.New("No more pumpkins")
      

      不要在代码中的任何位置使用errors.New 创建错误,而是在发生错误时返回预定义的值,然后您可以执行以下操作:

      package shop
      
      if err == fruits.NoMorePumpkins {
           ...
      }
      

      请参阅io 包错误以供参考。

      这可以通过添加隐藏检查实现的方法来改进,并使客户端代码更不受fruits包中的更改的影响。

      package fruits
      
      func IsNoMorePumpkins(err error) bool {
          return err == NoMorePumpkins
      } 
      

      请参阅os 包错误以供参考。

      【讨论】:

      • 这样做的一个缺点是另一个包可能会覆盖导出的NoMorePumpkins 变量的值。如果有办法使用const 做到这一点,那就太好了
      • @captncraig 那么你可以使用 getter 函数
      • @WingerSendon 好点。这可能是我最喜欢的解决方案。
      • 实际问题不是有人可以覆盖fruits.NoMorePumpkins,而是他们可能包装它,==会失败。
      • @cbednarski 查看 Go 1.13 的新内置错误。有一种新方法可以产生分层错误,因此您可以检查它是否被包装。
      【解决方案5】:

      为了补充@wst 的答案,在某些情况下,errors.Is(err, NotFound) 方法可能由于我也想弄清楚的原因而不起作用。如果有人知道,请在 cmets 中告诉我。

      但我找到了一种更好的方法,可以通过以下方式使用它:

      if NotFound.Is(err) {
          // do something
      }
      

      其中var NotFound = errors.New("not found") 是声明的导出常见错误。

      就我而言,解决方案是

      if models.GetUnAuthenticatedError().Is(err) {
          // Do something
      }
      

      【讨论】:

        【解决方案6】:

        试试

        err.Error() == "Token is expired"
        

        或者通过实现错误接口来创建你自己的错误。

        【讨论】:

        • 这是一个糟糕的建议。如果错误文本发生变化怎么办?您应该将其与错误 var 进行比较。
        • 错误文本通常是特定于实现的,不是包合同的一部分,因此不建议这样做。虽然如果在内部完成这是安全的(一旦你确保错误字符串不会改变),我会坚持使用自定义错误类型以保持一致性和可读性。
        • 我不知道为什么我的评论总是被删除!这个答案不是建议,所以不能是terrible advice。问题本身有一个字符串文字的比较。我试图回答这个问题,而不是提供最佳实践建议。
        【解决方案7】:

        包导出它们使用的错误变量以便其他人可以与它们进行比较是惯用的。

        例如如果错误来自名为 myPkg 的包并定义为:

        var ErrTokenExpired error = errors.New("Token is expired")
        

        您可以将错误直接比较为:

        if err == myPkg.ErrTokenExpired {
            log.Printf("Unauthorised: %s\n", err)
        }
        

        如果错误来自第三方包并且不使用导出的错误变量,那么您可以做的只是与您从 err.Error() 获得的字符串进行比较,但要小心这种方法,因为要更改错误字符串可能不会在主要版本中发布,并且会破坏您的业务逻辑。

        【讨论】:

        • 比比较字符串好多了。
        • 您不应该真正测试第三方错误,因为这些错误是实现细节。例如,如果您有一些数据库包并且本地实现使用了映射,则如果项目不存在(或者在 Go 的情况下,ok 将是 false),则可能会出现关键错误。如果它改为使用 SQL 后端,您可能会得到一个未找到行的错误。在我看来,包永远不应该直接传播错误。 a) 将它们映射到更合适的自定义错误类型(例如上一个示例的 item not found 错误),或者 b) 用自定义“实现错误”类型包装它,或者 c) 恐慌
        【解决方案8】:

        错误类型是接口类型。错误变量表示可以将自身描述为字符串的任何值。这是接口的声明:

        type error interface {
            Error() string
        }
        

        最常用的错误实现是errors包未导出的errorString类型:

        // errorString is a trivial implementation of error.
        type errorString struct {
            s string
        }
        
        func (e *errorString) Error() string {
            return e.s
        }
        

        查看此工作代码输出 (The Go Playground):

        package main
        
        import (
            "errors"
            "fmt"
            "io"
        )
        
        func main() {
            err1 := fmt.Errorf("Error")
            err2 := errors.New("Error")
            err3 := io.EOF
        
            fmt.Println(err1)         //Error
            fmt.Printf("%#v\n", err1) // &errors.errorString{s:"Error"}
            fmt.Printf("%#v\n", err2) // &errors.errorString{s:"Error"}
            fmt.Printf("%#v\n", err3) // &errors.errorString{s:"EOF"}
        }
        

        输出:

        Error
        &errors.errorString{s:"Error"}
        &errors.errorString{s:"Error"}
        &errors.errorString{s:"EOF"}
        

        另见:Comparison operators

        比较运算符比较两个操作数并产生一个无类型的布尔值 价值。在任何比较中,第一个操作数必须可分配给 第二个操作数的类型,反之亦然。

        相等运算符 ==!= 适用于 可比。

        指针值是可比较的。如果两个指针值相等 指向同一个变量,或者如果两者的值都为零。指向的指针 不同的零大小变量可能相等也可能不相等。

        接口值具有可比性。两个接口值相等,如果 它们具有相同的动态类型和相同的动态值,或者如果两者都有 值为零。

        非接口类型 X 的值 x 和接口类型 T 的值 t 当类型 X 的值是可比较的并且 X 实现时是可比较的 T. 如果 t 的动态类型与 X 和 t 相同,则它们相等 动态值等于 x。

        如果结构值的所有字段都是可比较的,则结构值是可比较的。二 如果相应的非空白字段是,则结构值相等 相等。


        所以:

        1- 你可以使用Error(),就像这个工作代码(The Go Playground):

        package main
        
        import (
            "errors"
            "fmt"
        )
        
        func main() {
            err1 := errors.New("Token is expired")
            err2 := errors.New("Token is expired")
            if err1.Error() == err2.Error() {
                fmt.Println(err1.Error() == err2.Error()) // true
            }
        }
        

        输出:

        true
        

        2- 您也可以将其与nil 进行比较,例如这个工作代码 (The Go Playground):

        package main
        
        import (
            "errors"
            "fmt"
        )
        
        func main() {
            err1 := errors.New("Token is expired")
            err2 := errors.New("Token is expired")
            if err1 != nil {
                fmt.Println(err1 == err2) // false
            }
        }
        

        输出:

        false
        

        3- 您也可以将其与完全相同的错误进行比较,例如此工作代码
        (The Go Playground):

        package main
        
        import (
            "fmt"
            "io"
        )
        
        func main() {
            err1 := io.EOF
            if err1 == io.EOF {
                fmt.Println("err1 is : ", err1)
            }
        }
        

        输出:

        err1 is :  EOF
        

        参考:https://blog.golang.org/error-handling-and-go

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2013-12-12
          • 2016-04-26
          • 1970-01-01
          • 2016-03-20
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多