服务粉丝

我们一直在努力
当前位置:首页 > 财经 >

Go版本大于1.13,程序里这样做错误处理才地道

日期: 来源:脑子进煎鱼了收集编辑:卡尔文_

大家好,这里是每周都在陪你进步的网管。

之前写过几篇关于 Go 错误处理的文章,发现文章里不少知识点都有点落伍了,比如Go在1.13后对错误处理增加了一些支持,最大的变化就是支持了错误包装(Error Wrapping),以前想要在调用链路的函数里包装错误都是用"github.com/pkg/errors"这个库。

Go 在2019年发布的Go1.13版本也采纳了错误包装,并且还提供了几个很有用的工具函数让我们能更好地使用包装错误。这篇文章就来主要说一下这方面的知识点,不过开始我们还是再次强调一下使用 Go Error 的误区,避免我们从其他语言切换过来时给自己后面挖坑。

自定义错误要实现error接口

这一条估计很多人都知道,但是文章开头开始先从这个惯例开始,因为我以前待过一个PHP转Go的研发团队,可能大家一开始都不太会,才有了这种错误的使用方式。

首先我们再复述一遍,Go通过error类型的值表示程序里的错误。

error类型是一个内建接口类型,该接口只规定了一个返回字符串值的Error方法。

type error interface {
    Error() string
}

Go程序的函数经常会返回一个error

package strconv

func Atoi(s string) (int, error) {
  ....
}

调用者通过测试error值是否是nil来进行错误处理。

i, err := strconv.Atoi("42")
if err != nil {
    fmt.Printf("couldn't convert number: %v\n", err)
    return
}
fmt.Println("Converted integer:", i)

errornil时表示成功;非nilerror表示失败。

说完 Go 里 error 最基本的使用方式后,接下来说项目里的自定义错误类型。假如项目在 Dao 层定义了一个这样的错误类型来记录数据库查询错误。

type MyError struct {
    Sql   string
    Param string
    Err   error
}

假如,这个自定义的MyError不去实现error接口,Dao 层里的函数返回的都是MyError的话。

func FindUserRowByPhoneMyError(userId int) (user User, MyError error) {
  ......
}

那么使用这些 Dao 函数的代码逻辑层都得引入dao.MyError这个额外的类型。有人会说,我把MyError定义在公共包里,所有代码逻辑层、Dao 层都用这个common.MyError总没啥问题了吧。

使用上乍一看没什么问题,但其实最大的问题就是不兼容、不符合Go语言对错误的接口约束,就没法对自定义错误类型使用Go对error提供的其他功能了,比如说后面要介绍的错误包装。

所以针对自定义的错误类型,我们也要让他变成一个真正的Go error,方法就是让它实现error接口定义的方法。

func (e *MyError) Error() string {
    return fmt.Sprintf("sql: %s, params: %s, err: %s", e.Sql, e.Param, e.Err.Error())
}

包装错误

在现实的程序应用里,一个逻辑往往要经多多层函数的调用才能完成,那在程序里我们的建议Error Handling 尽量留给上层的调用函数做,中间和底层的函数通过错误包装把自己要记的错误信息附加再原始错误上再返回给外层函数。

比如像下面这样

func doAnotherThing() error {
    return errors.New("error doing another thing")
}

func doSomething() error {
    err := doAnotherThing()
    return fmt.Errorf("error doing something: %v", err)
}

func main() {
    err := doSomething()
    fmt.Println(err)
}

这段代码从打印错误信息的输出上看没什么问题,但是深层次的问题很明显,我们丢失了原来的err,因为它已经被我们的fmt.Errorf函数转成一个新的字符串了。

基于这个背景,很多开源三方库提供了错误包装、追加错误调用栈等功能,用的最多的就是"github.com/pkg/errors"这个库,提供了下面几个主要的包装错误的功能。

//只附加新的信息
func WithMessage(err error, message string) error

//只附加调用堆栈信息
func WithStack(err error) error

//同时附加堆栈和信息
func Wrap(err error, message string) error

Go官方在2019年发布1.13版本,自己也增加了对错误包装的支持,不过并没有提供什么Wrap函数,而是扩展了fmt.Errorf函数,加了一个%w来生成一个包装错误。

e := errors.New("原始错误")
w := fmt.Errorf("外面包了一个错误%w", e)

Go1.13引入了包装错误后,同时为内置的errors包添加了3个函数,分别是UnwrapIsAs

先来聊聊Unwrap,顾名思义,它的功能就是为了获取到包装错误里那个被嵌套的error。

func Unwrap(err error) error {
    //先判断是否是wrapping error
 u, ok := err.(interface {
  Unwrap() error
 })
 //如果不是,返回nil
 if !ok {
  return nil
 }
 //否则则调用该error的Unwrap方法返回被嵌套的error
 return u.Unwrap()
}

这里需要注意的是,嵌套可以有很多层,我们调用一次errors.Unwrap函数只能返回往里一层的error,如果想获取更里面的,需要调用多次errors.Unwrap函数。最终如果一个error不是warpping error,那么返回的是nil

如果想得到最原始的error,建议自己封装个工具函数,类似这样

func Cause(err error) error {
 for err != nil {
    err = errors.Unwrap(err)
 }
 return err
}

对于我们文章开头定义的那个自定义错误MyError想要把它变成可包装的Error的话,还需要实现一个Unwrap()方法。

func (e *MyError) Unwrap() error { return e.Err }

有了包装错误后,像具体某种错误的判断和错误的类型转换也得需要跟进改一下才行。这就是errors包在1.13后新增的另外两个工具函数IsAs的作用。接下来我们一个个来说。

errors.Is

在Go 1.13之前没有包装错误的时候,程序里要判断是不是同一个error可以直接简单粗暴的:

if err == os.ErrNotExists {
  ......
}

这样我们就可以通过判断来做一些事情。但是现在有了包装错误后这样办法就不完美的,因为你根本不知道返回的这个err是不是一个嵌套的error,嵌套了几层。所以基于这种情况,Go为我们提供了errors.Is函数。

func Is(err, target error) bool
  • 如果err和目标错误target是同一个,那么返回true
  • 如果err 是一个包装错误,目标错误target也包含在这个嵌套错误链中的话,那么也返回true

下面是一个使用errors.Is判断是否是同一错误的例子。


var ErrDivideByZero = errors.New("divide by zero")

func Divide(a, b int) (int, error) {
    if b == 0 {
        return 0, ErrDivideByZero
    }
    return a / b, nil
}

func main() {
    a, b := 10, 0
    result, err := Divide(a, b)
    if err != nil {
        switch {
        case errors.Is(err, ErrDivideByZero):
            fmt.Println("divide zero error")
        default:
            fmt.Printf("unexpected division error: %+v\n", err)
        }
        return
    }

    fmt.Printf("%d / %d = %d\n", a, b, result)
}

errors.As

同样在没有包装错误前,我们要把error 转换为一个具体类型的error,一般都是使用类型断言或者 type switch,其实也就是类型断言。

if pathErr, ok := err.(*os.PathError); ok {
 fmt.Println(pathErr.Path)
}

但是有了包装错误之后,返回的err可能是已经被嵌套了,这种方式就不能用了,所以Go为我们在errors包里提供了As函数。

func As(err error, target interface{}) bool

As 函数所做的就是遍历错误的嵌套链,从里面找到类型符合的error,然后把这个error赋给target参数,这样我们在程序里就可以使用转换后的target了,因为这里有赋值,所以target必须是一个指针,这个也算是Go内置包里的一个惯例了,像json.Unmarshal也是这样。

所以把上面的例子用As 函数实现就变成了酱婶:

var pathErr *os.PathError
if errors.As(err, pathErr) {
 fmt.Println(pathErr.Path)
}

总结

这篇文章主要是更新一下Error处理在Go 1.13以后新增的功能点,以前的文章介绍的更多的还是使用"pkg/errors"那个包的方式,主要是前两年以前公司用的Go版本一直是1.12,所以这部分知识我一直没更新过来,这里简单做个梳理。

Go错误处理以前也写过几篇,建议大家一起看看,有搭建项目的需求时统一看一下。


觉得有用就点个在看 

相关阅读

  • 醒醒吧,未来不会有 Go2 了!

  • 大家好,我是煎鱼。马上春节了,节前最后一更。提前预祝大家春节快乐!本周末在学习的时候,看到 Go 团队大当家 Russ Cox(下称:rsc)在近期分享的《GopherCon 2022: Russ Cox - Compati
  • 紧急提醒!福州严查!

  • 2022年以来,全市市场监管部门深入开展整治校外教育培训机构不规范问题、减轻中小学生课外负担“点题整治”行动,查处了一批校外培训机构违法违规案件,促进全市校外培训机构健康
  • 突然窒息,仍在抢救……注意,最近高发

  • 1月14日湖北宜昌三峡中心人民医院西陵院区急诊科仅下半夜就收治了5名醉汉有的喝得人事不省有的呕吐得一塌糊涂在宜昌三峡中心人民医院伍家院区急诊科每天也都会收治醉汉所谓
  • 苦难的2022,仍需要避世的“主题公园”。

  • 点击收听-评《阿凡达2:水之道》:约5.3分杨超:5分《长江图》导演,《不要抬头》等19期嘉宾这便是“元宇宙电影”的雏形。当你们还在“科幻”或“电影”的维度讨论它是否合格之时,
  • 秒播TV端电视,近期最好用的版本!

  • 1、测试设备测评软件神袅电视软件适用TV端测评设备当贝X3测评结果无限制版本2、神袅电视【TV端】(软件链接在文章底部)很多小伙伴说,有没有新的电视直播分享一些?那么,那就安排一
  • UE4 材质练习 之 基础操作

  • 最近在学习 UE4 虚幻引擎,正好项目中也有用到,顺便记录一下相关内容,欢迎大家交流讨论,来不及解释了,快上车~~基础概念使用 UE 创建材质后,默认状态如下图所示,这里面有很多参数设
  • 聚合多功能,超强手机神器来了!

  • 1、测试设备测评软件红椒收音机软件适用安卓端测评设备小米12测评结果无需授权、海量资源2、红椒收音机【安卓端】(软件链接在文章底部)今天小编给大家整来一个安卓手机端全新
  • 如何才能学好音视频中的语音处理~~

  • 语音合成技术伴随着人工智能的浪潮,语音行业在快速发展,各大厂都在高薪寻觅从事语音技术的专业人才。作为语音行业中的音视频开发,有更多的从业者开始接触和学习语音合成技术。

热门文章

  • “复活”半年后 京东拍拍二手杀入公益事业

  • 京东拍拍二手“复活”半年后,杀入公益事业,试图让企业捐的赠品、家庭闲置品变成实实在在的“爱心”。 把“闲置品”变爱心 6月12日,“益心一益·守护梦想每一步”2018年四

最新文章

  • Go版本大于1.13,程序里这样做错误处理才地道

  • 大家好,这里是每周都在陪你进步的网管。之前写过几篇关于 Go 错误处理的文章,发现文章里不少知识点都有点落伍了,比如Go在1.13后对错误处理增加了一些支持,最大的变化就是支持了
  • 醒醒吧,未来不会有 Go2 了!

  • 大家好,我是煎鱼。马上春节了,节前最后一更。提前预祝大家春节快乐!本周末在学习的时候,看到 Go 团队大当家 Russ Cox(下称:rsc)在近期分享的《GopherCon 2022: Russ Cox - Compati
  • 三坑咨询|粉丝群&拼团交易群

  • 加入我们美好的时光里,我们彼此相伴成长,我们享受岁月静好,我们遇见,我们感恩,我们成长,感谢你,最美的时光,和我们在一起。QQ助力群:1009069207QQ出物群:1060724410QQ拼团群(不能发助力
  • Lolita|可爱百搭大头鞋

  • 2022年12月Lolita|大头鞋Q:什么是大头鞋?A:大头鞋就是“鞋头”比较大的鞋子。通常会设计为较宽的版型,较少出现挤脚的情况,视觉上看起来是比较圆润可爱的。而且大部分大头鞋的鞋底
  • 非汉服|冬季不妨看看保暖的民族风

  • 非汉服前文说明不知道今年是从什么时候开始,就刮起了"民族风"服饰的春风。当然,这里并不是说真的是我们少数民族的服饰,而是在汉服的基础上加入了民族元素,比如纹样、色彩风格、