前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >golang简单设计错误系统

golang简单设计错误系统

作者头像
超级大猪
发布2024-06-19 13:15:38
600
发布2024-06-19 13:15:38
举报
文章被收录于专栏:大猪的笔记大猪的笔记

go大量地使用错误,但错误系统一直饱受诟病,早期errors包中只有一个光秃秃的New方法,使得很多著名的项目如GRPC也只能使用偏门方法处理错误。

在1.13后,errors包中新增了 As/Is两个方法,同时,fmt.Errorf中可以使用 %w进行错误的封装,这使得搭建简单的错误系统方便起来。

代码语言:javascript
复制
    // fmt.Errorf error %w 封装
    err1 := fmt.Errorf("find error:%w", ErrUnknown)
    // 封装后的错误仍然是 ErrUnknown
    gtest.Assert(errors.Is(err1, ErrUnknown), true)

如何处理错误

一般情况下,当调用函数返回错误,我们会:

  1. 打印相关的调试信息,如错误的string,行号,堆栈等
  2. 将错误返回至更上层,直至用户
  3. 如果是致命错误,则直接调用Fatal终止程序。

1 中打印相关信息可以统一在最外层中间件打印,而不要直接在获得错误的时候打印。这样就能避免多次打印重复的内容,这是代码规范的范畴。

2 中返回错误,则可以使用fmt.Errorf层层包装更多的信息。

直接定义大法

最简单的错误体系,是在包的开头用New定义一堆基础错误,比如io/io.go中有这些定义:

代码语言:javascript
复制
var errInvalidWrite = errors.New("invalid write result")

var ErrShortBuffer = errors.New("short buffer")

var EOF = errors.New("EOF")
...

不要动态地定义错误,而应该使用%w封装基础错误类型。因为动态地定义错误会让错误的判定变得复杂:

代码语言:javascript
复制
// bad
str, err := f.Read()
if err != nil {
    return fmt.Errorf("getFile error: %v", err) // 上层无法简单地判定错误的类型
}

所有其它的错误利用fmt.Errorf对基础错误进行层层封装:

代码语言:javascript
复制
str, err := f.Read() // 比如这个自定义的实现中会返回eof
if errors.Is(err, io.EOF) {
    // 注意这个是%w,且只允许出现一次,返回到上层
    return fmt.Errorf("getFile error: %w", err) 
}

使用%w封装错误,返回到上层的错误仍然可以使用errors.Is进行判定。

在这个体系中,错误要么是预定义的基础错误,要么是基础错误通过fmt.Errorf的封装,十分简单。

用户可以:

  1. 判定错误的基础类型(使用errors.Is)
  2. 获取层层附加的error message,通过 err.Error()

定义错误码

在微服务中,返回一个错误码可以方便服务间的判定。

这时errors.New定义的错误就不太够用了。需要定义一个结构实现Error接口:

代码语言:javascript
复制
type BaseError struct {
    ErrStr string
    Code   int
}

func (e *BaseError) Error() string {
    return fmt.Sprintf("errorMsg:%s, code:%d", e.ErrStr, e.Code)
}


func FromError(err error) (code int, has bool) {
    if target := (&BaseError{}); errors.As(err, &target) {
        return target.Code, true
    }

    if err != nil {
        // return unknown code
        return 1, true
    }

    return -1, false
}

const (
    // 未知错误
    ErrCodeUnknown = iota + 1 // 从1开始
    // 超时
    ErrCodeTimeOut
)

var ErrUnknown = &BaseError{
    ErrStr: "unknown",
    Code:   ErrCodeUnknown,
}

var ErrTimeOut = &BaseError{
    ErrStr: "timeout",
    Code:   ErrCodeTimeOut,
}
......

FromError(err error) (code int, has bool) 类似grpc的status.FromError,检查是否存在错误并获得错误码:

代码语言:javascript
复制
// fmt.Errorf error %w 封装
err1 := fmt.Errorf("find error:%w", ErrUnknown)
// 封装后的错误仍然是这个类型
gtest.Assert(errors.Is(err1, ErrUnknown), true)

// As 用法
if code, hasErr := FromError(err1); hasErr {
    t.Logf("target code:%v", code)
    gtest.Assert(code, 1)
}

在这个体系中,错误要么是预定义的BaseError,要么是BaseError通过fmt.Errorf的封装

并且可获取到最初始定义的错误码,方便服务间的错误处理。

到这里,这个错误系统已经能满足大部分的使用场景,且保持了简单。简单的东西不容易出错且易在团队中推广和使用,这也是go很多官方库的设计思路。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-06-18 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 如何处理错误
  • 直接定义大法
  • 定义错误码
相关产品与服务
消息队列 TDMQ
消息队列 TDMQ (Tencent Distributed Message Queue)是腾讯基于 Apache Pulsar 自研的一个云原生消息中间件系列,其中包含兼容Pulsar、RabbitMQ、RocketMQ 等协议的消息队列子产品,得益于其底层计算与存储分离的架构,TDMQ 具备良好的弹性伸缩以及故障恢复能力。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档