It's hilarious... Can another baby in Go error handling solve the trouble?

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, and the space to manipulate is relatively small.

This means that all we can do with an error is to declare a value of type error by appending the content of the error 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 use fmt.Errorfto wrap errors, this just adds to the textual output, not something the caller can check programmatically.
  • You can write a complex error wrapper structure that contains the metadata you want to check to work with error.Is, and.As to allow the caller to access the root cause of the error..Unwrap

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.Unwrapto traverse the list. In the case of linked list storage, there is a problem of sequence.

With 函数中,other 参数的错误将会放在包装错误列表的头部。如果在调用 With 函数时是 With(b->a, d->c),呈现在内的错误列表是:d->c->b->a。

对应的使用场景:

  • errors.Is(errors.With(err, other)):
    • 判别标准:errors.Is(other) || errors.Is(err)。
  • errors.As(errors.With(err, other), target):
    • 判别标准:errors.As(other, target) || errors.As(err, target)
  • errors.With(err, other).Error():
    • 输出结果是 other.Error() + ": " + err.Error()。

提案作者@Nate Finch 希望通过这种错误包装方式,对既有的代码改动是最小的。也能提供最广泛的功能适用性,认为是有价值的。

案例

场景

作者给出了一个非常经典的用户案例。在我们平时写应用代码时,在写过的每个 go 应用程序中都看到了它。

应用中有一个返回特定域错误的包,例如返回 pq.ErrNoRows 的 postgres 驱动程序。

您希望将该错误向上传递到堆栈以维护原始错误的上下文,但您不希望调用者必须知道 postgres 错误才能知道如何从存储层处理此错误。

改造

可以使用新的 With 函数,您可以通过众所周知的错误类型添加元数据,以便可以一致地检查您的函数返回的错误,而不管底层实现如何。

如下代码:

// 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
}
复制代码

这种错误通常称为哨兵错误。

总结

今天给大家介绍的这个提案,还是比较贴合我们日常工作中的使用场景的。平时写 Go 应用程序,思考的多,就会折腾这个问题。会出现,莫非要根据错误文本来判断错误内容?

因此像是业内错误库,或是之前看毛老师讲的,都会进行相关的设计。这份提案也是一个不错的补充了。

文章持续更新,可以微信搜【脑子进煎鱼了】阅读,本文 GitHub github.com/eddycjy/blo… 已收录,学习 Go 语言可以看 Go 学习地图和路线,欢迎 Star 催更。

推荐阅读

参考

Guess you like

Origin juejin.im/post/7101159289053511710