关于 Go 错误处理的新提案

大家好,我是煎鱼 。
在 Go 的编程中 , 错误处理机制的处理永远是大家在讨论 。不过 Go1 没法大动干戈了,那就想办法继续优化吧 。
今天煎鱼给大家介绍一个五一假期期间学习时看到的一个新提案 。
如下图:
背景
在现阶段,我们在标准库中能够包装错误的唯一方法是使用 fmt. 。可以操作的空间是比较小的 。
这意味着我们对错误所能做的就是将错误内容添加到其 .Error() 输出中 , 以此声明 error 类型的值 。
如下代码:
err := fmt.Errorf("煎鱼:%s", errors.New("放假中"))if err != nil {}
但业务诉求往往没那么简单 。这个时候如果我们希望在收到错误信息时,返回堆栈并提供其他信息(例如:业务状态码)时,就没有什么特别简单的方法 。
硬要做,只有如下 3 种方案:
现在最靠谱的是第 3 种方式,最完整,对应的是在 Go1.13 新增的 error 系列方法,还在青壮年阶段(多年来唯一新增的错误处理补全) 。
提案的原作者认为现阶段还是不够简单方便 。
新提案
新提案是希望在标准库中实现一个更简单的函数来达到上述第 3 点的效果,支持将任何错误与任何其他错误包装在一起 , 从而使它们形成一个新的包装错误列表 。
如下代码:
// With returns an error that wraps err with other.func With(err, other error) error
这个被包裹起来的错误类似于链表 , 可以复用 . 来遍历列表 。而类链表存储 , 就有先后顺序的问题 。
在 With 函数中,other 参数的错误将会放在包装错误列表的头部 。如果在调用 With 函数时是 With(b->a, d->c),呈现在内的错误列表顺序将会是:d->c->b->a 。
【关于 Go 错误处理的新提案】对应的使用场景:
.As(.With(err, other), ):
.With(err, other).Error():
提案作者@Nate Finch 希望通过这种错误包装方式,对既有的代码改动是最小的 。也能提供最广泛的功能适用性,认为是有价值的 。

关于 Go 错误处理的新提案

文章插图
案例 场景
作者给出了一个非常经典的用户案例 。在我们平时写应用代码时,在写过的每个 go 应用程序中都看到了它 。应用中有一个返回特定域错误的包,例如返回 pq. 的驱动程序 。
你希望将该错误向上传递到堆栈以维护原始错误的上下文,但你不希望调用者必须知道错误才能知道如何从存储层处理此错误 。
改造
为此可以使用新的 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.Errorif errors.As(err, &pqErr) && pqErr.Constraint == "unique_user_name" {return errors.With(err, flags.Conflict)}if err != nil {// some other unknown errorreturn fmt.Errorf("error setting name on user with id %v: %w", err) }return nil}
业内将这种错误称为哨兵错误 。
总结
今天给大家介绍的这个提案 , 还是比较贴合我们日常工作中的使用场景的 。平时写 Go 应用程序,思考的多,就会折腾这个问题 。会出现,莫非要根据错误文本来判断错误内容?
像是业内错误库,或是之前看毛老师讲的 , 都会进行相关的设计 。这份提案也是一个不错的补充了 。
参考
关注煎鱼,获取业内第一手消息和知识