Golang中Error的设计与实践详解

 更新时间:2023年08月17日 11:31:24   作者:用户4129026645654  
这篇文章主要为大家详细介绍了Golang中Error的设计以及是具体如何处理错误的相关知识,文中的示例代码简洁易懂,需要的小伙伴可以跟随小编一起学习一下

如果你对于 Go 的 Error 设计不太熟悉也不习惯,为什么许多接口都需要返回 error 接口类型的值呢?什么时候该处理 error,什么时候该抛出 error,什么时候又该忽略 error ?Go 设计者又为什么要这样设计 error 呢?想必刚接触 Golang 的同学也会和我一样有类似的疑惑,在读了 TGPL 以及 Go Blog 相关的章节/内容后,我尝试回答一下这些问题。

在第 1 、2小节我将尝试回答 error 是什么,它是如何设计的,以及为什么这样设计。

在第 3 小节我将回答在 Coding 时,如何处理错误。

Error 是什么

在 Go built-in 包中,Error 被设计为一个接口。

// The error built-in interface type is the conventional interface for
// representing an error condition, with the nil value representing no error.
type error interface {
   Error() string
}

Go 的设计理念是:失败(failure)只是一种常见的行为。

因此对于那些失败被视作理所当然的函数可以返回一个额外的结果——error,通常是最后一个返回值。而如果失败只有一种可能的原因,那么只需要返回一个 bool 值即可。

上述做法在 Go 的源码或接口设计中很常见。举两个例子:

以常见的 Reader 接口为例, Read 方法读取至多 len(p) 个字节到 p 中,返回读取的字节数 n 和读取过程中可能发生的错误 err。

type Reader interface {
   Read(p []byte) (n int, err error)
}

当我们使用 map 时经常遇到的一种情况是:确定某个键是否在 map 中。但是 map 在该键不存在时也会返回默认值,此时可以使用带 bool 返回值的形式:

if val, ok := m["key"]; ok {
    // do something
} else {
    // do other things
}

Error 的设计

Go 的错误处理设计与其他语言的异常不同。Go 中的 error 就是一个普通的值对象,而其他语言如 Java 中的 Exception 将会造成程序控制流的终止和其他行为,Exception 与普通的值不同。虽然 Go 也有类似的异常机制 —— panic,但它仅用于报告完全无法预料的错误(可能有 Bug),而不应该是一个健壮程序应该返回的程序错误(这一点与 Java 等语言不同)。

关于 Go 为什么这样设计的原因,Kernighan 在 《The Go Programming Language》中给出解释:"The reason for this design is that exceptions tend to entangle the description of an error with the control flow required to handle it, often leading to an undesirable outcome: routine errors are reported to the end user in the form of an incomprehensible stack trace, full of information about the structure of the program but lacking intelligible context about what went wrong"。

即:因为异常会将错误的描述和处理错误的控制流纠缠在一起,通常会导致程序错误以一种难以理解的栈追踪的方式被报告给终端用户,这种方式充满了程序结构的信息,但是缺少关于哪里出错的易于理解的上下文信息。

相反,Go 程序使用普通的程序控制流机制如 if 以及 return 来对 error 作出响应,这种设计虽然要求 Gophers 更加关注错误处理逻辑,但这正是它想做到的点。即“好的程序应该考虑到所有可能的错误,并且对其进行处理”。

Go 将 error 设计为一个接口,只需要实现 Error() string 方法,返回有意义、简练的错误描述信息即可。这也使得我们可以以任何的方式来自定义错误。

Tips: 建议在底层只需要返回清晰地错误信息,每一层包裹一些重要并且简洁的上下文信息,并且最终在程序的顶层或者某一个不得不处理的层级处理该错误。

正是这种方式,在 Go 中也将这种层层包裹的错误称之为错误链。由此,在 Go 1.13 之后出现了一些新的设计以支持这种错误链的处理方式。其中最简单的错误链就是如下所述的层层包裹的文本信息(或者程序调用栈信息)

genesis: crashed: no parachute: G-switch failed: bad relay orientation

Error 处理策略

最常见的做法是传递错误。即将被调用程序产生的错误传递给调用方,由上层决定如何处理,并且如果需要的话可以附加一些本程序所知的上下文信息。

obj, err := doSomething()
if err != nil {
    return err
}
// do otherthings

第二种,对于那些表示短暂的、难以预测的错误,可以去重试该操作,当然要施加一定的重试次数限制

for i := 0; i < times; i++ {
    res, err := run()
    if err == nil {
        return res
    }
    // do something like log or metrics
}

第三种,如果无法继续往下执行,调用方打印错误信息并且优雅地结束程序

if err := initT(); err != nil {
    panic("something wrong") // though this way is not elegant
}

第四种,在某些情况错误并不致命,也可以只是记录下错误并且继续往下执行。这种情况,最多导致程序缺少部分功能,但总比什么都不做要好。

obj, err := doSomething()
if err != nil {
    logs.CtxInfo(ctx, "something wrong but it doesnot cause serious consequences")
}
// and continue to do something

最后一种,调用方确信不可能发生的错误或者即使发生了也不会有任何问题,可以忽略它。

// could not encode failed
bytes, _ := json.Marshal(obj)

最后,Go 的错误处理比较特别,一般在检查错误之后,先处理失败情况然后再处理成功情形——"Happy Path"。可以保证所有错误均被处理之后再开心的处理正常情形(提醒 Programmer 不要忘记处理异常情况),并且可以减少缩进层级(在其他语言也被称为 "Guard"模式)。

obj, err := getObj()
if err != nil {
    // do some err handling policy
    return fmt.Errorf("could not get obj, err = %v", err)
}
// happy path

到此这篇关于Golang中Error的设计与实践详解的文章就介绍到这了,更多相关golang error内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 一文详解go的defer和return的执行顺序

    一文详解go的defer和return的执行顺序

    go的defer和return是golang中的两个关键字,return用于返回函数的返回值,也可以参与一定的流程控制,defer是golang中的延迟调用,经常用于文件流的关闭,锁的解锁操作,本文给大家介绍了go的defer和return的执行顺序,需要的朋友可以参考下
    2024-07-07
  • Win7环境下搭建Go开发环境(基于VSCode编辑器)

    Win7环境下搭建Go开发环境(基于VSCode编辑器)

    这篇文章主要介绍了Win7环境下搭建Go开发环境(基于VSCode编辑器),需要的朋友可以参考下
    2017-02-02
  • Golang WebSocket创建单独会话详细实例

    Golang WebSocket创建单独会话详细实例

    这篇文章主要给大家介绍了关于Golang WebSocket创建单独会话的相关资料,WebSocket 协议主要为了解决基于 HTTP/1.x 的 Web 应用无法实现服务端向客户端主动推送的问题,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2023-11-11
  • Golang图片验证码的使用方法

    Golang图片验证码的使用方法

    最近在使用到Golang进行原生开发,注册和登录页面都涉及到图片验证码的功能,支持很多类型的验证方式,例如支持数字类型、字母类型、音频验证码、中文验证码,本文给大家介绍Golang图片验证码的使用,感兴趣的朋友跟随小编一起看看吧
    2024-05-05
  • golang实现大文件读取的代码示例

    golang实现大文件读取的代码示例

    在实际工作,我们需要读取大数据文件,文件可能上G百G,所以我们不可能一次性的读取到内存,接下来本文给大家介绍了golang实现大文件读取的示例,需要的朋友可以参考下
    2024-04-04
  • golang并发编程使用Select语句的实现

    golang并发编程使用Select语句的实现

    Go语言中的select语句是并发编程中的重要工具,允许Goroutine等待多个通道操作,它阻塞直至任一case可执行,可用于接收数据、实现超时机制和非阻塞通道操作,感兴趣的可以了解一下
    2024-10-10
  • Go语言基础学习之map的示例详解

    Go语言基础学习之map的示例详解

    哈希表是常见的数据结构,有的语言会将哈希称作字典或者映射,在Go中,哈希就是常见的数据类型map,本文就来聊聊Golang中map的相关知识吧
    2023-04-04
  • go proto编译引用外部包问题解决方案示例

    go proto编译引用外部包问题解决方案示例

    这篇文章主要为大家介绍了go proto编译引用外部包问题解决方案示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-10-10
  • 构建go镜像实现过程全面讲解

    构建go镜像实现过程全面讲解

    这篇文章主要为大家介绍了构建go镜像实现过程全面讲解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-12-12
  • Go Module常用命令及如何使用Go Module

    Go Module常用命令及如何使用Go Module

    go module是go官方自带的go依赖管理库,在1.13版本正式推荐使用,这篇文章主要介绍了Go Module常用命令及如何使用Go Module,需要的朋友可以参考下
    2024-02-02

最新评论