Go语言是否有标准的错误变量?

huangapple go评论77阅读模式
英文:

Does Go have standard Err variables?

问题

刚开始使用Golang。我认为在错误结构中声明一个错误变量并在其中使用它来确定出了什么问题是符合惯例的,就像在strconv.go中所做的那样。在那里,声明了ErrRangeErrSyntax,并且在适当的时候,将对它们的引用存储在返回时的NumError结构中。我认为这样做的原因是因为可以将存储在NumError中的错误引用的地址与ErrRangeErrSyntax变量进行比较,以确定返回的是哪种类型的错误。

是否有类似的“标准”错误类型?例如,在Java中,你有像java.lang.IllegalArgumentException这样的东西。例如,是否有ErrArgumentErrUnsupportedOperation,我可以在自己的代码中使用,而不是每次都创建意义相同的新错误变量?

英文:

Just started using Golang. I think that it is idiomatic to declare an error variable and use it in your error structure to determine what went wrong, as is done in strconv.go. There, ErrRange and ErrSyntax is declared, and when appropriate, references to those are stored in NumError structs when they return. I think that the reason is because then the address of the reference to the error stored in NumError can be compared with the ErrRange and ErrSyntax variables to determine which type of error was returned.

Are there "standard" such declared error types? In Java, for example, you have things like java.lang.IllegalArgumentException. Is there, for instance, ErrArgument or ErrUnsupportedOperation that I can use in my own code instead of creating new error variables that mean the same thing every time?

答案1

得分: 33

有几种常见的惯用方法可以让包的作者返回错误。

  1. 固定的错误变量,通常命名为Err...
var (
    ErrSomethingBad = errors.New("some string")
    ErrKindFoo      = errors.New("foo happened")
)
  1. 错误类型,通常命名为...Error
type SomeError struct {
    // 额外的信息,对调用者可能有用
    // (或用于在`Error()`中生成漂亮的消息)
    ExtraInfo int
}
type OtherError string

func (e SomeError) Error() string { /* ... */ }
func (e OtherError) Error() string {
    return fmt.Sprintf("failure doing something with %q", string(e))
}

在Go 1.13及更高版本中,您还可以实现一个Unwrap() error方法,以便与errors.Unwrap一起使用。

  1. 根据需要使用临时的errors.New值。
func SomepackageFunction() error {
    return errors.New("not implemented")
}
  1. 使用标准包中定义的错误。通常仅限于一小组错误,如io.EOF;在大多数情况下,最好使用上述方法1创建自己的错误。
func SomeFunc() error {
    return io.EOF
}

请注意,有时在实现接口时(例如成为io.ReaderRead方法),最好使用匹配的错误(或根据接口规范“必需”)。

  1. 创建一个接口,例如net.Error
type Error interface {
    error
    Timeout() bool   // 错误是否为超时?
    Temporary() bool // 错误是否为临时错误?
}
  1. 在Go 1.13或更高版本中,使用简单上下文返回现有错误(对于更复杂的上下文,请使用具有Unwrap()方法的自定义错误类型):
func SomepackageFunction() error {
    err := somethingThatCanFail()
    if err != nil {
        return fmt.Errorf("some context: %w", err)
    }
}

请注意,Go 1.13中引入的新格式化动词%w,它将提供的错误包装起来,以便调用者可以使用errors.Unwraperror.Is访问它。

通常,您会同时使用这些方法的组合。

如果您认为您的包的任何用户将曾经想要测试特定的错误,那么首选第一、第二和第五种方法。
它们允许像这样的操作:

err := somepkg.Function()
if err == somepkg.ErrSomethingBad {
    // ...
}
// 或者对于错误类型,类似这样的操作:
if e, ok := err.(somepkg.SomeError); ok && e.ExtraInfo > 42 {
    // 如果需要,使用`e`的字段/方法
}

对于Go 1.13或更高版本,上述代码可以改写为:

err := somepkg.Function()
if errors.Is(err, somepkg.ErrSomethingBad) {
    // ...
}
// 或者对于错误类型,类似这样的操作:
var someErr somepkg.SomeError
if errors.As(err, &someErr) && someErr.ExtraInfo > 42 {
    // 如果需要,使用`someErr`的字段/方法
}

区别在于错误将根据需要进行解包。

第五种方法(只是第二种方法的扩展)允许像这样检查错误的行为/类型(或使用Go 1.13的errors.As):

if e, ok := err.(net.Error); ok && e.Timeout() {
    // 这是一个超时,休眠并重试
}

第三种方法的问题在于它没有合理的方法让包的用户进行测试(测试err.Error()返回的字符串的内容不是一个好主意)。
然而,对于您不希望任何人想要测试的错误,这是可以接受的。

进一步阅读:

英文:

There are a few common idiomatic ways for a package author to make error returns.

  1. Fixed error variables, usually named Err…

    var (
            ErrSomethingBad = errors.New("some string")
            ErrKindFoo      = errors.New("foo happened")
    )
    
  2. Error types, usually named …Error

    type SomeError struct {
         // extra information, whatever might be useful to callers
         // (or for making a nice message in `Error()`)
         ExtraInfo int
    }
    type OtherError string
    
    func (e SomeError) Error() string { /* … */ }
    func (e OtherError) Error() string {
            return fmt.Sprintf("failure doing something with %q", string(e))
    }
    

With Go 1.13 and later you may also want to implement a Unwrap() error method for use with errors.Unwrap.

  1. Ad hoc errors.New values as needed.

    func SomepackageFunction() error {
            return errors.New("not implemented")
    }
    
  2. Using errors defined in the standard packages. Usually limited to a small set such as io.EOF; in most cases it's better to create your own via method 1 above.

    func SomeFunc() error {
            return io.EOF
    }
    

    Note that sometimes when implementing an interface (such as a Read method to become an io.Reader) it is best to use matching errors (or "required" by the specification of the interface).

  3. Making an interface such as net.Error:

    type Error interface {
        error
        Timeout() bool   // Is the error a timeout?
        Temporary() bool // Is the error temporary?
    }
    
  4. With Go 1.13 or later, returning an existing error with simple context (for more complicated context, use a custom error type with an Unwrap() method):

    func SomepackageFunction() error {
        err := somethingThatCanFail()
        if err != nil {
                return fmt.Errorf("some context: %w", err)
        }
    }
    

    Note the new (to Go 1.13) formatting verb %w, it wraps the provided error so that callers can get at it with errors.Unwrap or error.Is.

Often you'll use a mix of all these ways.

The first, second, and fifth are preferred if you think any user of your package will ever want to test for specific errors.
They allow things like:

err := somepkg.Function()
if err == somepkg.ErrSomethingBad {
        // …
}
// or for an error type, something like:
if e, ok := err.(somepkg.SomeError); ok && e.ExtraInfo > 42 {
        // use the fields/methods of `e` if needed
}

For Go 1.13 or later, the above can instead be written as:

err := somepkg.Function()
if errors.Is(err, somepkg.ErrSomethingBad) {
        // …
}
// or for an error type, something like:
var someErr somepkg.SomeError
if errors.As(err, &someErr) && someErr.ExtraInfo > 42 {
        // use the fields/methods of `someErr` if needed
}

the difference is that errors will be unwrapped as needed.

The fifth way (which is just an extension of the second) allows checking the error for behaviour/type like so (or using Go 1.13's errors.As):

if e, ok := err.(net.Error); ok && e.Timeout() {
        // it's a timeout, sleep and retry
}

The problem with the third way is it leaves no sane way for a user of the package to test for it. (Testing the contents of the string returned by err.Error() isn't a great idea).
However, it's fine for the errors that you don't ever expect anyone to want to test for.

Further reading:

答案2

得分: 3

没有,只需提供可理解的错误信息,而不是通用的错误信息。IllegalArgument传递了什么信息?不多,不够。

英文:

No, there aren't. Just provide intelligible errors instead of generic ones. What information does a IllegalArgument transport? Not much, not enough.

答案3

得分: 2

正如你所看到的,特定的包使用特定的错误。例如,在database/sql包中,它们定义了:

var ErrNoRows = errors.New("sql: no rows in result set")

所以如果你使用QueryRow(它将错误推迟到Scan),然后使用Scan,你可以这样做:

if err := row.Scan(&data); err != nil && err != sql.ErrNoRows {
    // 真的出错了
} else if err != nil {
    // 没有结果
} else {
    // 找到了
}

os/exec包有var ErrNotFound = errors.New("executable file not found in $PATH")

encoding/json包有一个type UnmarshalTypeError,它只是一个实现了error接口的类型。

所以,虽然没有"一组标准错误",但你可以(而且很可能应该)有特定的错误变量进行重用。

你可以有自己的errorMsgs包,其中可以重用常见的错误:

err := doSomething()
if err != nil {
    switch err {
        case errorMsgs.IllegalArgument:
           // 做某事
        case errorMsgs.CouldNotConnect:
           // 做其他事情
     }
}
英文:

As you have seen, there are specific errors that specific packages use. For example, in the database/sql package, they define:

var ErrNoRows = errors.New("sql: no rows in result set")

So if you do QueryRow (which defers the error until Scan), and then Scan, you can do

if  err := row.Scan(&data); err != nil && err != sql.ErrNoRows {
    //something actually went wrong
} else if err != nil {
    //no results
} else {
    //we found it
}

os/exec has var ErrNotFound = errors.New("executable file not found in $PATH")

encoding/json has a type UnmarshalTypeError which is just a type that implements the error interface.

So no, while there is no "set of standard errors", you can (and most likely should) have specific error variables that you reuse.

You could have your own errorMsgs package that you use, where you can reuse common errors:

err := doSomething(); if err != nil {
    switch err {
        case errorMsgs.IllegalArgument:
           //do something
        case errorMsgs.CouldNotConnect:
           //do something else
     }
}

huangapple
  • 本文由 发表于 2015年5月12日 05:21:12
  • 转载请务必保留本文链接:https://go.coder-hub.com/30177860.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定