如何比较Go错误

ryevplcw  于 2022-12-07  发布在  Go
关注(0)|答案(9)|浏览(122)

我有一个错误值,当打印在控制台上时,该值为Token is expired
如何将其与特定的错误值进行比较?我尝试了以下方法,但没有效果:

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

qc6wkl3g1#

声明错误并将其与' == '(如err == myPkg.ErrTokenExpired)进行比较不再是Go 1.13(Q3 2019)的最佳实践
发行说明提到:
Go语言1.13包含了对**错误 Package **的支持,这是在Error Values proposal中首次提出并在associated issue中讨论过的。
通过提供返回wUnwrap方法,错误e可以 Package 另一个错误w
ew都可用于程序,允许ew提供额外的上下文或重新解释它,同时仍然允许程序基于w做出决策。
为了支持 Package ,fmt.Errorf现在有一个用于创建 Package 错误的%w动词,并且errors包中的三个新函数(errors.Unwraperrors.Iserrors.As)简化了展开和检查 Package 错误的过程。
因此Error Value FAQ解释道:

您需要做好准备,以防收到的错误被 Package 。

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

if err == io.ErrUnexpectedEOF

变成

if errors.Is(err, io.ErrUnexpectedEOF)
  • 检查err != nil是否需要更改。
  • io.EOF的比较不需要更改,因为io.EOF不应被 Package 。

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

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

变成

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

还可以使用此模式检查错误是否实现了接口。(这是一种非常罕见的情况,此时指向接口的指针是合适的。)
将类型开关重写为序列if-elses

vjhs03f7

vjhs03f72#

此答案适用于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套件错误以取得指涉。

ctzwtxfj

ctzwtxfj3#

尝试

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

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

7gyucuyw

7gyucuyw4#

对于包来说,导出它们使用的错误变量是惯用的,这样其他人就可以与它们进行比较。
例如,如果错误来自名为myPkg的软件包,并且定义为:

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

您可以直接比较以下错误:

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

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

t3psigkw

t3psigkw5#

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

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"}

另请参阅:比较运算子
比较运算符比较两个操作数并生成一个无类型的布尔值。在任何比较中,第一个操作数必须可赋值给第二个操作数的类型,反之亦然。
相等运算子==!=适用于可比较的算子。
指针值是可比较的。如果两个指针指向同一个变量或都具有值nil,则它们的值相等。指向不同的零大小变量的指针可能相等,也可能不相等。
接口值是可比较的。如果两个接口值具有相同的动态类型和相等的动态值,或者都具有值nil,则这两个接口值相等。
当类型X的值是可比较的并且X实现T时,非接口类型X的值x和接口类型T的值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

xtupzzrd

xtupzzrd6#

不鼓励按字符串比较错误,而应该按值比较错误。

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
}

这是一种更详细但更安全的方法。

4xrmg8kj

4xrmg8kj7#

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

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

但是在某些情况下,函数返回的错误信息会比较复杂,例如,一个错误会被多次 Package ,在每次函数调用时都会添加一个上下文,比如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,则可能会遇到nil指针取消引用运行时错误。

kdfy810k

kdfy810k8#

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

但我发现了一个更好的方法来使用它在以下方式是为我工作:

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

其中var NotFound = errors.New("not found")是已声明的导出常见错误。
在我的情况下,解决方案是

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

sczxawaw9#

我想发布一个案例,其中errors.Is可以很好地处理non-comparable values的自定义错误。

type CustomError struct {
    Meta    map[string]interface{}
    Message string
}

func (c CustomError) Error() string {
    return c.Message
}

var (
    ErrorA = CustomError{Message: "msg", Meta: map[string]interface{}{"key": "value"}}
)

func DoSomething() error {
    return ErrorA
}

func main() {
    err := DoSomething()
    if errors.Is(err, ErrorA) {
        fmt.Println("error is errorA")
    } else {
        fmt.Println("error is NOT errorA")
    }
}

输出量

error is NOT errorA

Playground
原因是errors.Is检查target是否可比较

func Is(err, target error) bool {
    if target == nil {
        return err == target
    }

    isComparable := reflectlite.TypeOf(target).Comparable()

Go语言中的comparable类型有
布尔值、数字、字符串、指针、通道、可比较类型的数组、字段都是可比较类型的结构
由于CustomErrorMeta map[string]interface{}不具有可比性,因此errors.Is检查失败。
一种解决方法是将ErrorA = &CustomError{Message: "msg", Meta: map[string]interface{}{"key": "value"}}声明为指针。

相关问题