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语言中定时任务库Cron的使用

    一文带你入门Go语言中定时任务库Cron的使用

    在平时的开发需求中,我们经常会有一些重复执行的操作需要触发执行,说白了就是定时任务。这篇文章主要给大家介绍一下如何在go项目中实现一个crontab功能,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助
    2022-08-08
  • Go语言的WebAssembly实战开发

    Go语言的WebAssembly实战开发

    Go语言的WebAssembly支持为Web开发带来了新的可能性,本文主要介绍了Go语言与WebAssembly的结合,内容包括编译方法、JavaScript交互、性能优化技巧和实战案例,感兴趣的可以了解一下
    2026-05-05
  • go-zero 应对海量定时/延迟任务的技巧

    go-zero 应对海量定时/延迟任务的技巧

    这篇文章主要介绍了go-zero 如何应对海量定时/延迟任务,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-10-10
  • Go语言实现单端口转发到多个端口

    Go语言实现单端口转发到多个端口

    这篇文章主要为大家详细介绍了Go语言实现单端口转发到多个端口,文中的示例代码讲解详细,具有一定的参考价值,对大家的学习或工作有一定的帮助,需要的小伙伴可以了解下
    2024-02-02
  • 浅析Golang中调度器的关键机制与性能

    浅析Golang中调度器的关键机制与性能

    Golang的调度器是其并发模型的核心组件,负责管理Goroutine的调度和执行,本文将从理论和代码层面分析Golang调度器的关键机制,感兴趣的可以了解下
    2025-03-03
  • Golang strings包常用字符串操作函数

    Golang strings包常用字符串操作函数

    Golang 中的字符串统一使用 UTF-8 (属于Unicode编码的一种实现方式)进行编码,本篇文章将结合具体实例对常用的字符串操作函数进行介绍,感兴趣的可以了解一下
    2021-12-12
  • Go语言使用SQLite实现本地存储的示例详解

    Go语言使用SQLite实现本地存储的示例详解

    在开发工具类软件、桌面应用或者移动端时,我们经常需要一个轻量级数据库来做本地存储,本文将带你一步步用 Go 搭建一个 SQLite 本地存储的示例,希望对大家有所帮助
    2025-08-08
  • go常用指令之go mod详解

    go常用指令之go mod详解

    当go命令运行时,它查找当前目录然后查找相继的父目录来找出 go.mod,下面这篇文章主要给大家介绍了关于go常用指令之go mod的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2022-08-08
  • 使用Go http重试请求的示例

    使用Go http重试请求的示例

    开发中对于http请求是经常遇到,一般可能网络延迟或接口返回超时,这篇文章主要介绍了使用Go http重试请求的示例,需要的朋友可以参考下
    2022-08-08
  • Golang实现SSH、SFTP操作小结

    Golang实现SSH、SFTP操作小结

    在日常的一些开发场景中,我们需要去和远程服务器进行一些通信,本文主要介绍了Golang实现SSH、SFTP操作小结,具有一定的参考价值,感兴趣的可以了解一下
    2024-04-04

最新评论