【go学习笔记】Go errors 最佳实践

news2024/11/30 2:34:52

文章目录

    • 一、Error Type
      • 1. Sentinel Error(预定义Error字符串错误值)
        • 1.1 缺点
      • 2. Error types(错误类型)
        • 2.1 缺点
      • 3. Opaque errors(不透明错误)
        • 3.1 Assert errors for behaviour, not type
    • 二、Handling Error
      • 1. Indented flow is for errors
      • 2. Eliminate error handling by eliminating errors
        • 2.1 示例1
        • 2.2 示例2
        • 2.3 示例3
      • 3. Wrap erros
        • 3.1 存在的问题1
        • 3.2 存在的问题2
        • 3.3 pkg/errors 库
        • 3.4 pkg/errors 库最佳实践(最重要)
        • 3.5 总结
    • 三、Go 1.13 errors
      • 1. Errors before Go 1.13
      • 2. Go1.13 errors标准库
        • 2.1 Unwrap
        • 2.2 Wrapping errors with %w
        • 2.3 Customizing error tests with Is and As methods
        • 2.4 Errors and package APIs
        • 2.5 errors & github.com/pkg/errors
    • 四、References

一、Error Type

1. Sentinel Error(预定义Error字符串错误值)

预定义的特定错误,我们叫为 sentinel error,这个名字来源于计算机编程中使用一个特定值来表示不可能进行进一步处理的做法。所以对于 Go,我们使用特定的值来表示错误。

if err == ErrSomething {}

类似的 io.EOF,更底层的 syscall.ENOENT。

在这里插入图片描述

使用 sentinel 值是最不灵活的错误处理策略,因为调用方必须使用 == 将结果与预先声明的值进行比较。当您想要提供更多的上下文时,这就出现了一个问题,因为返回一个不同的错误将破坏相等性检查。甚至是一些有意义的 fmt.Errorf 携带一些上下文,也会破坏调用者的 == ,调用者将被迫查看 error.Error() 方法的输出,以查看它是否与特定的字符串匹配

1.1 缺点
  • 依赖检查 error.Error 的输出。
    不应该依赖检测 error.Error 的输出,Error 方法存在于 error 接口主要用于方便程序员使用,但不是程序(编写测试可能会依赖这个返回)。这个输出的字符串用于记录日志、输出到 stdout 等
  • Sentinel errors 成为你 API 公共部分。
    如果您的公共函数或方法返回一个特定值的错误,那么该值必须是公共的,当然要有文档记录,这会增加API 的表面积。
    如果 API 定义了一个返回特定错误的 interface,则该接口的所有实现都将被限制为仅返回该错误,即使它们可以提供更具描述性的错误。比如 io.Reader。像 io.Copy 这类函数需要 reader 的实现者比如返回 io.EOF 来告诉调用者没有更多数据了,但这又不是错误。
  • Sentinel errors 在两个包之间创建了依赖。
    sentinel errors 最糟糕的问题是它们在两个包之间创建了源代码依赖关系。例如,检查错误是否等于io.EOF,您的代码必须导入 io 包。这个特定的例子听起来并不那么糟糕,因为它非常常见,但是想象一下,当项目中的许多包导出错误值时,存在耦合,项目中的其他包必须导入这些错误值才能检查特定的错误条件(in the form of an import loop)。

结论: 尽可能避免 sentinel errors。
我的建议是避免在编写的代码中使用 sentinel errors。在标准库中有一些使用它们的情况,但这不是一个您应该模仿的模式。

2. Error types(错误类型)

Error type 是实现了 error 接口的自定义类型。例如 MyError 类型记录了文件和行号以展示发生了什么。
在这里插入图片描述
因为 MyError 是一个 type,调用者可以使用断言转换成这个类型,来获取更多的上下文信息。

在这里插入图片描述
与错误值相比,错误类型的一大改进是它们能够包装底层错误以提供更多上下文。一个不错的例子就是 os.PathError 它提供了底层执行了什么操作、那个路径出了什么问题。
在这里插入图片描述

2.1 缺点

调用者要使用类型断言和类型 switch,就要让自定义的 error 变为 public。这种模型会导致和调用者产生强耦合,从而导致 API 变得脆弱。
结论是尽量避免使用 error types,虽然错误类型比 sentinel errors 更好,因为它们可以捕获关于出错的更多上下文,但是 error types 共享 error values 许多相同的问题。
因此,我的建议是避免错误类型,或者至少避免将它们作为公共 API 的一部分。

3. Opaque errors(不透明错误)

在我看来,这是最灵活的错误处理策略,因为它要求代码和调用者之间的耦合最少。
我将这种风格称为不透明错误处理,因为虽然您知道发生了错误,但您没有能力看到错误的内部。作为调用者,关于操作的结果,您所知道的就是它起作用了,或者没有起作用(成功还是失败)。
这就是不透明错误处理的全部功能–只需返回错误而不假设其内容。

在这里插入图片描述

3.1 Assert errors for behaviour, not type

在少数情况下,这种二分错误处理方法是不够的。例如,与进程外的世界进行交互(如网络活动),需要调用方调查错误的性质,以确定重试该操作是否合理。在这种情况下,我们可以断言错误实现了特定的行为,而不是断言错误是特定的类型或值。考虑这个例子:
在这里插入图片描述
在这里插入图片描述
可以看到,外部需要依赖底层错误类型的Temporary()方法,可以在错误包中提供调用该方法判断的函数从而隐藏暴露的底层错误类型细节。
在这里插入图片描述
这里的关键是,这个逻辑可以在不导入定义错误的包或者实际上不了解 err 的底层类型的情况下实现——我们只对它的行为感兴趣

二、Handling Error

1. Indented flow is for errors

无错误的正常流程代码,将成为一条直线,而不是缩进的代码。

在这里插入图片描述
这里更推荐第一种判断错误的方式。

2. Eliminate error handling by eliminating errors

2.1 示例1

下面的代码有啥问题?

在这里插入图片描述
上述代码可以简化为以下写法:
在这里插入图片描述

2.2 示例2

统计 io.Reader 读取内容的行数

在这里插入图片描述
改进版本:

在这里插入图片描述

2.3 示例3

在这里插入图片描述
上述代码中需要大量的判断err != nil的场景,通过封装Writer对象,可以讲对于err的判断转移至实现的Write函数中,改进版本如下:

在这里插入图片描述
在这里插入图片描述

3. Wrap erros

3.1 存在的问题1

还记得之前我们 auth 的代码吧,如果 authenticate 返回错误,则 AuthenticateRequest 会将错误返回给调用方,调用者可能也会这样做,依此类推。在程序的顶部,程序的主体将把错误打印到屏幕或日志文件中,打印出来的只是:没有这样的文件或目录。
在这里插入图片描述
没有生成错误的 file:line 信息。没有导致错误的调用堆栈的堆栈跟踪。这段代码的作者将被迫进行长时间的代码分割,以发现是哪个代码路径触发了文件未找到错误。
在这里插入图片描述
但是正如我们前面看到的,这种模式与 sentinel errors 或 type assertions 的使用不兼容,因为将错误值转换为字符串,将其与另一个字符串合并,然后将其转换回 fmt.Errorf 破坏了原始错误,导致等值判定失败。

3.2 存在的问题2

you should only handle errors once. Handling an error means inspecting the error value, and making a single decision.
在这里插入图片描述
我们经常发现类似的代码,在错误处理中,带了两个任务: 记录日志并且再次返回错误。
在这里插入图片描述
在这个例子中,如果在 w.Write 过程中发生了一个错误,那么一行代码将被写入日志文件中,记录错误发生的文件和行,并且错误也会返回给调用者,调用者可能会记录并返回它,一直返回到程序的顶部。
在这里插入图片描述
在这里插入图片描述
上述代码输出结果为:

unable to write: io.EOF
could not write config: io.EOF

这种错误没有任何意义,无法帮助定位问题。同时,不应该在每一个内部调用返回 err 时就记录一次日志,而应该只在最外层的调用判断 err != nil 时记录日志,因为只有在这里记录的日志才是最完整的。

除了上述结果,程序员忘记将 err 进行 return 可能也会导致系统存在潜在的bug。

Go 中的错误处理契约规定,在出现错误的情况下,不能对其他返回值的内容做出任何假设。由于JSON 序列化失败,buf 的内容是未知的,可能它不包含任何内容,但更糟糕的是,它可能包含一个半写的 JSON 片段。
由于程序员在检查并记录错误后忘记 return,损坏的缓冲区将被传递给 WriteAll,这可能会成功,因此配置文件将被错误地写入。但是,该函数返回的结果是正确的。

在这里插入图片描述

3.3 pkg/errors 库

日志记录与错误无关且对调试没有帮助的信息应被视为噪音,应予以质疑。记录的原因是因为某些东西失败了,而日志包含了答案。

  • The error has been logged.(错误要被日志记录)
  • The application is back to 100% integrity. (应用程序处理错误,保证100%完整性)
  • The current error is not reported any longer. (之后不再报告当前错误)

使用 github.com/pkg/errors 库

在这里插入图片描述

fmt.Errorf 用了占位符 %w 之后创建的也是 wrapError 类型。上例中的Wrap方法调用处可改写为:fmt.Errorf(“%w\nopen failed”, err)

输出结果为:
在这里插入图片描述
通过使用 pkg/errors 包,您可以向错误值添加上下文,这种方式既可以由人也可以由机器检查。
在这里插入图片描述
在这里插入图片描述

3.4 pkg/errors 库最佳实践(最重要)

只有最底层产生错误原因的位置才需要调用一次Wrap,上层链路调用该底层函数的位置只需要简单返回该err即可。

  • 在你的应用代码中,使用 errors.New 或者 errors.Errorf 返回错误。
    在这里插入图片描述
  • 如果调用其他包内的函数,通常简单的直接返回。
    在这里插入图片描述
  • 如果和其他第三方库进行协作,考虑使用 errors.Wrap 或者 errors.Wrapf 保存堆栈信息。同样适用于和标准库协作的时候。
    在这里插入图片描述
  • 非底层真正产生错误的位置直接返回错误,而不是每个错误产生的地方到处打日志。
  • 在程序的顶部或者是工作的 goroutine 顶部(请求入口),使用 %+v 把堆栈详情记录。
    在这里插入图片描述
  • 使用 errors.Cause 获取 root error,再进行和 sentinel error 判定。
3.5 总结
  • Packages that are reusable across many projects only return root error values.
    选择 wrap error 是只有 applications 可以选择应用的策略。具有最高可重用性的包只能返回根错误值。此机制与 Go 标准库中使用的相同(kit 库的 sql.ErrNoRows)。Warpped error应该只有业务代码才应该使用
  • If the error is not going to be handled, wrap and return up the call stack.
    这是关于函数/方法调用返回的每个错误的基本问题。如果函数/方法不打算处理错误,那么用足够的上下文 wrap errors 并将其返回到调用堆栈中。例如,额外的上下文可以是使用的输入参数或失败的查询语句。确定您记录的上下文是足够多还是太多的一个好方法是检查日志并验证它们在开发期间是否为您工作。
  • Once an error is handled, it is not allowed to be passed up the call stack any longer.
    一旦确定函数/方法将处理错误,错误就不再是错误。如果函数/方法仍然需要发出返回,则它不能返回错误值。它应该只返回零(比如降级处理中,你返回了降级数据,然后需要 return nil)。

简单来说就是,错误发生的最底层Warp一下,中间层直接透传,最顶层打日志。

三、Go 1.13 errors

1. Errors before Go 1.13

最简单的错误检查
在这里插入图片描述
有时我们需要对 sentinel error 进行检查

在这里插入图片描述
实现了 error interface 的自定义 error struct,进行断言使用获取更丰富的上下文
在这里插入图片描述
函数在调用栈中添加信息向上传递错误,例如对错误发生时发生的情况的简要描述。
在这里插入图片描述
使用创建新错误 fmt.Errorf 丢弃原始错误中除文本外的所有内容。正如我们在上面的 QueryError中看到的那样,我们有时可能需要定义一个包含底层错误的新错误类型,并将其保存以供代码检查。程序可以查看 QueryError 值以根据底层错误做出决策。这里是 QueryError:
在这里插入图片描述
在这里插入图片描述

2. Go1.13 errors标准库

2.1 Unwrap

go1.13为 errors 和 fmt 标准库包引入了新特性,以简化处理包含其他错误的错误。其中最重要的是: 包含另一个错误的 error 可以实现返回底层错误的 Unwrap 方法。如果 e1.Unwrap() 返回 e2,那么我们说 e1 包装 e2,您可以展开 e1 以获得 e2。
按照此约定,我们可以为上面的 QueryError 类型指定一个 Unwrap 方法,该方法返回其包含的错误:
在这里插入图片描述
go1.13 errors 包包含两个用于检查错误的新函数:Is 和 As。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.2 Wrapping errors with %w

如前所述,使用 fmt.Errorf 向错误添加附加信息。
在这里插入图片描述
在 Go 1.13 中 fmt.Errorf 支持新的 %w 谓词。
在这里插入图片描述
用 %w 包装错误可用于 errors.Is 以及 errors.As。
在这里插入图片描述
内部代码实现:
在这里插入图片描述

2.3 Customizing error tests with Is and As methods

Is方法实现源码:
在这里插入图片描述
可以自定义一个Error类型,并实现它的Is方法:
在这里插入图片描述

2.4 Errors and package APIs

之前写法与现在写法对比:
在这里插入图片描述

注意:%w 不包含堆栈上下文,更推荐使用pkg\errors库的Wrap方法

2.5 errors & github.com/pkg/errors

可以使用pkg\errors的Wrap方法封装官方标准库的errors,从而实现打印堆栈信息。
在这里插入图片描述

四、References

https://www.infoq.cn/news/2012/11/go-error-handle/
https://golang.org/doc/faq#exceptions
https://www.ardanlabs.com/blog/2014/10/error-handling-in-go-part-i.html
https://www.ardanlabs.com/blog/2014/11/error-handling-in-go-part-ii.html
https://www.ardanlabs.com/blog/2017/05/design-philosophy-on-logging.html
https://medium.com/gett-engineering/error-handling-in-go-53b8a7112d04
https://medium.com/gett-engineering/error-handling-in-go-1-13-5ee6d1e0a55c
https://rauljordan.com/2020/07/06/why-go-error-handling-is-awesome.html
https://morsmachine.dk/error-handling
https://crawshaw.io/blog/xerrors
https://dave.cheney.net/2012/01/18/why-go-gets-exceptions-right
https://dave.cheney.net/2015/01/26/errors-and-exceptions-redux
https://dave.cheney.net/2014/11/04/error-handling-vs-exceptions-redux
https://dave.cheney.net/2014/12/24/inspecting-errors
https://dave.cheney.net/2016/04/07/constant-errors
https://dave.cheney.net/2019/01/27/eliminate-error-handling-by-eliminating-errors
https://dave.cheney.net/2016/06/12/stack-traces-and-the-errors-package
https://dave.cheney.net/2016/04/27/dont-just-check-errors-handle-them-gracefully
https://blog.golang.org/errors-are-values
https://blog.golang.org/error-handling-and-go
https://blog.golang.org/go1.13-errors
https://commandcenter.blogspot.com/2017/12/error-handling-in-upspin.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1095890.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

详解RocketMQ消息存储原理

本文基于RocketMQ 4.6.0进行源码分析 一. 存储概要设计 RocketMQ存储的文件主要包括CommitLog文件、ConsumeQueue文件、Index文件。RocketMQ将所有topic的消息存储在同一个文件中,确保消息发送时按顺序写文件,尽最大的能力确保消息发送的高性能与高吞吐…

SpringBoot实战(二十五)集成 Shiro

目录 一、Shiro 简介1.1 Shiro 定义1.2 Shiro 核心组件1.3 Shiro 认证过程 二、SpringBoot集成2.1 集成思路2.2 Maven依赖2.3 自定义 Realm2.4 Shiro 配置类2.5 静态资源映射2.6 AuthController2.7 User 实体2.8 用户接口类2.9 用户接口实现类2.10 OrderController(…

手把手教你使用Python从零开始搭建感知器

大家好,今天本文将展示如何从零开始实现神经网络的最基本要素(感知器),以及人工智能的基本模块背后的数学原理。 虽然人工智能和机器学习等术语已经成为流行词汇,每天都会听到或谈论这些概念,但它们背后的…

软件开发介绍

一、软件开发整体介绍 作为一名软件开发工程师,我们需要了解在软件开发过程中的开发流程,以及软件开发过程中涉及到的岗位角色,角色的分工、职责,并了解软件开发中涉及到的三种软件环境。 1.1 软件开发流程 第一阶段&#xff1a…

MQTT解读【全网最易懂】

目录 前言 一、MQTT相比于TCP长连接的优势 1、协议更标准 2、MQTT协议制定好了很多利于物联网的功能 3、理解数据内容,用数据产生价值 二、选择MQTT还是TCP长连接透传 1、原始的业务场景 2、端对端M2M场景——无人汽车 3、APP控制设备端场景——智能家居、智…

RK3588的GPU驱动和桌面环境

这里主要是以orange pi 5 plus为对象作一个简单的笔记 首先看rk3588的gpu介绍,它用的是ARM的GPU,支持openGL ES和openCL(支持什么其实跟GPU驱动有关,arm官方闭源GPU驱动就只支持这两个) opi官方提供了debian的xfce和…

Linux网络编程系列之服务器编程——多路复用模型

Linux网络编程系列 (够吃,管饱) 1、Linux网络编程系列之网络编程基础 2、Linux网络编程系列之TCP协议编程 3、Linux网络编程系列之UDP协议编程 4、Linux网络编程系列之UDP广播 5、Linux网络编程系列之UDP组播 6、Linux网络编程系列之服务器编…

ROS-6.参数的使用

参数的使用 参数服务结构命令行的使用方式运行小海龟命令介绍查看参数获取参数值设置参数保存参数到文件从文件导入参数 通过程序操作创建节点修改cmake编译运行 参数服务结构 ros中存在参数服务管理服务,管理这所有参数,所有节点剋订阅和发布这些节点 …

第三章 内存管理 三、覆盖与交换

目录 一、覆盖技术 二、交换技术 三、总结 一、覆盖技术 1、在覆盖技术中,我们要找到程序的调用结构。 2、因为这些程序不可能同时被调用(互斥调用),所以我们只需要选出需要空间最大的程序。 3、在物理内存中开拓一片与最大程…

ABB机器人关于重定位移动讲解

关于机器人如何重定位移动,首先来看一下示教器上的重定位移动是在哪。 从图中所示的坐标位置和操纵杆方向得知,重定位的本质是绕X、Y、Z轴的旋转。那么实现跟摇杆一样的操作,就可以通过改变当前位置的欧拉角来实现,参考Rapid指令…

小米笔记本Pro 15.6“频繁蓝屏解决办法

一、事情的缘起 2020年3月,我在小米官网购买这个笔记本,型号为:小米笔记本Pro 15.6" 2019款 四核i5 8G MX250 深灰。当时买这款笔记本,也是考虑到它屏幕比较大,而且配置也不错,四核8G的内存也足够我办…

测试需要写测试用例吗?

如何理解软件的质量 我们都知道,一个软件从无到有要经过需求设计、编码实现、测试验证、部署发布这四个主要环节。 需求来源于用户反馈、市场调研或者商业判断。意指在市场行为中,部分人群存在某些诉求或痛点,只要想办法满足这些人群的诉求…

并行Stream的性能测试

final long count 200_000_000;Random random new Random();//创建2亿条的listList<Integer> list Stream.generate(() -> random.nextInt(20)).limit(count).collect(Collectors.toList());// 顺序处理long startTime System.currentTimeMillis();list.stream().…

C语言联合体和枚举

C语言联合体和枚举 文章目录 C语言联合体和枚举一、联合体①联合体简介②联合体大小的计算 二、枚举 一、联合体 ①联合体简介 union Un {char c;int i; };像结构体一样&#xff0c;联合体也是由⼀个或者多个成员构成&#xff0c;这些成员可以不同的类型。但是编译器只为最大…

【Python数据分析工具】

文章目录 概要整体架构流程技术名词解释 概要 数据分析是一种通过收集、处理、分析和解释大量数据&#xff0c;以发现有价值信息、洞察趋势、制定决策并解决问题的过程。在现代科技和互联网的推动下&#xff0c;数据分析变得日益重要。它不仅仅是对数字和图表的简单解释&#…

GCOV覆盖率分析

安全之安全(security)博客目录导读 覆盖率分析汇总 目录 一、GCOV简介 二、GCOV使用示例 三、GCOV编译命令 四、运行并生成覆盖率报告 五、覆盖率报告分析 一、GCOV简介 因为动态代码分析可能只覆盖部分代码&#xff0c;所以我们需要一个代码覆盖工具&#xff0c;以了解…

APP备案避坑指南,值得收藏

目录 什么时间节点前需完成备案&#xff1f; APP/小程序一定要做备案吗&#xff1f; 涉及前置审批的APP有哪些&#xff1f; APP 支持安卓、IOS 多个运行平台&#xff0c;应该备案多少次&#xff1f; 企业是自有服务器&#xff0c;该如何进行APP备案&#xff1f; APP备案可…

探索服务器的无限潜能:创意项目、在线社区与更多可能

文章目录 1. 创意项目的孵化器1.1 托管你的应用1.2 测试和开发1.3 制定和实施你的计划 2. 构建在线社区2.1 自定义社交网络2.2 数据控制2.3 扩展和改进 3. 其他创意可能性3.1 博客和媒体网站3.2游戏服务器3.3 云存储3.4 数据分析3.5 远程办公 结论 &#x1f389;欢迎来到Java学…

你不一定知道的七种进程间通信方式

一、前言 每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到&#xff0c;所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程A把数据从用户空间拷贝到内核缓冲区,进程B再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信…

Easysearch压缩模式深度比较:ZSTD+source_reuse的优势分析

引言 在使用 Easysearch 时&#xff0c;如何在存储和查询性能之间找到平衡是一个常见的挑战。Easysearch 具备多种压缩模式&#xff0c;各有千秋。本文将重点探讨一种特别的压缩模式&#xff1a;zstd source_reuse&#xff0c;我们最近重新优化了 source_reuse,使得它在吞吐量…