2

Hello everyone, I am fried fish.

In Go programming, the handling of error handling mechanisms is always discussed. But Go1 can't make a big move, so let's find a way to continue to optimize.

Today, Fried Fish will introduce to you a new proposal that I saw when I was studying during the May Day holiday.

background

At this stage, the only way we can wrap bugs in the standard library is to use fmt.Errorf , which is relatively small.

This means that all we can do with the error is to declare a value of type error by appending the error content to its .Error() output.

The following code:

 err := fmt.Errorf("煎鱼:%s", errors.New("放假中"))
    if err != nil {}

But business demands are often not that simple. At this time, if we want to return to the stack and provide other information (for example: business status code) when we receive an error message, there is no particularly simple way.

There are only 3 options as follows:

  • You can return a new other error, but lose the contextual information of the original error.
  • You can wrap errors with fmt.Errorf which just adds textual output, not something the caller can check programmatically.
  • You can write a complex error wrapping structure that contains the metadata you want to check to work using error.Is , .As and .Unwrap to allow The root cause of the caller access error.

Now the most reliable method is the third method, which is the most complete. It corresponds to the error series methods added in Go1.13, and is still in the young and middle-aged stage (the only new error handling completion added for many years).

The original author of the proposal believes that it is still not simple and convenient at this stage.

new proposal

The new proposal is to implement a simpler function in the standard library errors to achieve the effect of point 3 above, supporting wrapping any error with any other error so that they form a new list of wrapped errors.

The following code:

 // With returns an error that wraps err with other.  
func With(err, other error) error

This wrapped error is similar to a linked list and can be reused errors.Unwrap to traverse the list. In the case of linked list storage, there is a problem of sequence.

In the With function, the other parameter's error will be placed at the head of the wrapping error list. If the call to the With function is With(b->a, d->c) , the error list presented is: d->c->b->a.

Corresponding usage scenarios:

  • errors.Is(errors.With(err, other)):

    • Criterion: errors.Is(other) || errors.Is(err).
  • errors.As(errors.With(err, other), target):

    • Criterion: errors.As(other, target) || errors.As(err, target)
  • errors.With(err, other).Error():

    • The output is other.Error() + ": " + err.Error().

Proposal author @Nate Finch hopes that through this error packaging, changes to existing code are minimal. It also provides the widest functional applicability and is considered valuable.

case

Scenes

The author gives a very classic user case. We see it in every go application we've ever written when we usually write application code.

There is a package in the application that returns domain specific errors, such as the postgres driver which returns pq.ErrNoRows.

You want to pass the error up the stack to maintain the context of the original error, but you don't want the caller to have to know about the postgres error to know how to handle it from the storage layer.

Retrofit

With the new With function, you can add metadata by well-known error types so that errors returned by your function can be checked consistently, regardless of the underlying implementation.

The following code:

 // SetUserName sets the name of the user with the given id. This method returns 
// flags.NotFound if the user isn't found or flags.Conflict if a user with that
// name already exists. 
func (st *Storage) SetUserName(id uuid.UUID, name string) error {
    err := st.db.SetUser(id, "name="+name)
    if errors.Is(err, pq.ErrNoRows) {
       return nil, errors.With(err, flags.NotFound)
    }
    var pqErr *pq.Error
    if errors.As(err, &pqErr) && pqErr.Constraint == "unique_user_name" {
        return errors.With(err, flags.Conflict)
    }
    if err != nil {
       // some other unknown error
       return fmt.Errorf("error setting name on user with id %v: %w", err) 
    }
    return nil
}

Such errors are often referred to as sentinel errors.

Summarize

The proposal introduced to you today is more suitable for the usage scenarios in our daily work. Usually writing Go applications, thinking a lot, will toss this problem. It will appear, is it necessary to judge the content of the error based on the error text?

Therefore, like the error library in the industry, or what Mr. Mao said before, relevant designs will be carried out. This proposal is also a good addition.

The article is updated continuously, you can read it by searching on WeChat [Brain fried fish]. This article has been included in GitHub github.com/eddycjy/blog . If you are learning Go language, you can see the Go learning map and route . Welcome to Star to urge you to update.

Recommended reading

refer to


煎鱼
8.4k 声望12.8k 粉丝