【Go语言最佳实践】错误只处理一次

我想提一下你应该只处理错误一次。 处理错误意味着检查错误值并做出单一决定。

// WriteAll writes the contents of buf to the supplied writer.
func WriteAll(w io.Writer, buf []byte) {w.Write(buf)
}

如果你做出的决定少于一个,则忽略该错误。 正如我们在这里看到的那样, w.WriteAll 的错误被丢弃。

但是,针对单个错误做出多个决策也是有问题的。 以下是我经常遇到的代码。

func WriteAll(w io.Writer, buf []byte) error {_, err := w.Write(buf)if err != nil {log.Println("unable to write:", err) // annotated error goes to log filereturn err                           // unannotated error returned to caller}return nil
}

在此示例中,如果在 w.Write 期间发生错误,则会写入日志文件,注明错误发生的文件与行数,并且错误也会返回给调用者,调用者可能会记录该错误并将其返回到上一级,一直回到程序的顶部。

调用者可能正在做同样的事情

func WriteConfig(w io.Writer, conf *Config) error {buf, err := json.Marshal(conf)if err != nil {log.Printf("could not marshal config: %v", err)return err}if err := WriteAll(w, buf); err != nil {log.Println("could not write config: %v", err)return err}return nil
}

因此你在日志文件中得到一堆重复的内容,

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

但在程序的顶部,虽然得到了原始错误,但没有相关内容。

err := WriteConfig(f, &conf)
fmt.Println(err) // io.EOF

我想深入研究这一点,因为作为个人偏好, 我并没有看到 logging 和返回的问题。

func WriteConfig(w io.Writer, conf *Config) error {buf, err := json.Marshal(conf)if err != nil {log.Printf("could not marshal config: %v", err)// oops, forgot to return}if err := WriteAll(w, buf); err != nil {log.Println("could not write config: %v", err)return err}return nil
}

很多问题是程序员忘记从错误中返回。正如我们之前谈到的那样,Go 语言风格是使用 guard clauses 以及检查前提条件作为函数进展并提前返回。

在这个例子中,作者检查了错误,记录了它,但忘了返回。这就引起了一个微妙的错误。

Go 语言中的错误处理规定,如果出现错误,你不能对其他返回值的内容做出任何假设。由于 JSON 解析失败,buf 的内容未知,可能它什么都没有,但更糟的是它可能包含解析的 JSON 片段部分。

由于程序员在检查并记录错误后忘记返回,因此损坏的缓冲区将传递给 WriteAll,这可能会成功,因此配置文件将被错误地写入。但是,该函数会正常返回,并且发生问题的唯一日志行是有关 JSON 解析错误,而与写入配置失败有关。

为错误添加相关内容

发生错误的原因是作者试图在错误消息中添加 context 。 他们试图给自己留下一些线索,指出错误的根源。

让我们看看使用 fmt.Errorf 的另一种方式。

func WriteConfig(w io.Writer, conf *Config) error {buf, err := json.Marshal(conf)if err != nil {return fmt.Errorf("could not marshal config: %v", err)}if err := WriteAll(w, buf); err != nil {return fmt.Errorf("could not write config: %v", err)}return nil
}func WriteAll(w io.Writer, buf []byte) error {_, err := w.Write(buf)if err != nil {return fmt.Errorf("write failed: %v", err)}return nil
}

通过将注释与返回的错误组合起来,就更难以忘记错误的返回来避免意外继续。

如果写入文件时发生 I/O 错误,则 errorError() 方法会报告以下类似的内容;

could not write config: write failed: input/output error
使用 github.com/pkg/errors 包装 errors

fmt.Errorf 模式适用于注释错误 message,但这样做的代价是模糊了原始错误的类型。 我认为将错误视为不透明值对于松散耦合的软件非常重要,因此如果你使用错误值做的唯一事情是原始错误的类型应该无关紧要的面孔

  1. 检查它是否为 nil
  2. 输出或记录它。

但是在某些情况下,我认为它们并不常见,您需要恢复原始错误。 在这种情况下,使用类似我的 errors 包来注释这样的错误, 如下

func ReadFile(path string) ([]byte, error) {f, err := os.Open(path)if err != nil {return nil, errors.Wrap(err, "open failed")}defer f.Close()buf, err := ioutil.ReadAll(f)if err != nil {return nil, errors.Wrap(err, "read failed")}return buf, nil
}func ReadConfig() ([]byte, error) {home := os.Getenv("HOME")config, err := ReadFile(filepath.Join(home, ".settings.xml"))return config, errors.WithMessage(err, "could not read config")
}func main() {_, err := ReadConfig()if err != nil {fmt.Println(err)os.Exit(1)}
}

现在报告的错误就是 K&D [11]样式错误,

could not read config: open failed: open /Users/dfc/.settings.xml: no such file or directory

并且错误值保留对原始原因的引用。

func main() {_, err := ReadConfig()if err != nil {fmt.Printf("original error: %T %v\n", errors.Cause(err), errors.Cause(err))fmt.Printf("stack trace:\n%+v\n", err)os.Exit(1)}
}

因此,你可以恢复原始错误并打印堆栈跟踪;

original error: *os.PathError open /Users/dfc/.settings.xml: no such file or directory
stack trace:
open /Users/dfc/.settings.xml: no such file or directory
open failed
main.ReadFile/Users/dfc/devel/practical-go/src/errors/readfile2.go:16
main.ReadConfig/Users/dfc/devel/practical-go/src/errors/readfile2.go:29
main.main/Users/dfc/devel/practical-go/src/errors/readfile2.go:35
runtime.main/Users/dfc/go/src/runtime/proc.go:201
runtime.goexit/Users/dfc/go/src/runtime/asm_amd64.s:1333
could not read config

使用 errors 包,你可以以人和机器都可检查的方式向错误值添加上下文。 如果昨天你来听我的演讲,你会知道这个库在被移植到即将发布的 Go 语言版本的标准库中。

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

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

相关文章

爬虫入门一

文章目录 一、什么是爬虫?二、爬虫基本流程三、requests模块介绍四、requests模块发送Get请求五、Get请求携带参数六、携带请求头七、发送post请求八、携带cookie方式一:放在请求头中方式二:放在cookie参数中 九、post请求携带参数十、模拟登…

HTTPS网络通信协议基础

目录 前言: 1.HTTPS协议理论 1.1协议概念 1.2加密 2.两类加密 2.1对称加密 2.2非对称加密 3.引入“证书” 3.1证书概念 3.2数据证书内容 3.3数据签名 4.总结 前言: 了解完HTTP协议后,HTTPS协议是HTTP协议的升级加强版&#xff0c…

基于Java的大学社团管理平台

功能介绍 平台采用B/S结构,后端采用主流的Springboot框架进行开发,前端采用主流的Vue.js进行开发。 整个平台包括前台和后台两个部分。 前台功能包括:首页、社团详情、申请加入、用户中心模块。后台功能包括:社团管理、分类管理…

Webpack和Rollup区别、使用场景、如何选择

Webpack 和 Rollup 都是前端构建工具,但它们的设计理念、侧重点和适用场景有所不同: Webpack 设计理念与功能: Webpack 是一个全能型的模块打包工具,不仅支持 JavaScript 模块的打包,还能处理 CSS、HTML、图片等各种静…

设计模式二:代理模式

1、什么是动态代理 可能很多小伙伴首次接触动态代理这个名词的时候,或者是在面试过程中被问到动态代理的时候,不能很好的描述出来,动态代理到底是个什么高大上的技术。不方,其实动态代理的使用非常广泛,例如我们平常使…

unity 使用VS Code 开发,VS Code配置注意事项

vscode 对应的插件(unity开发) 插件:.Net Install Tool,c#,c# Dev Kit,IntelliCode For C# Dev Kit,Unity,Unity Code Snippets 本人现在是用了这些插件 unity需要安装Visual Studio Editor 1、.Net Install Tool 设置 需要在设置里面配置…

人机、态势与感知之间的双向阈值开关及其交变特性

人-机双向阈值开关是一种可以根据阈值条件控制人和机器之间交互的装置或系统。它可以根据事先设定的条件来切换人和机器之间的工作模式。在这个开关中,有两个阈值,一个是人的阈值,另一个是机器的阈值。当人的阈值被触发时,系统将进…

jvm gc日志拿取与分析思路

前言 参考文章:Java中9种常见的CMS GC问题分析与解决 - 美团技术团队 排查过程 进入容器里 生产应用是跑在docker上的,所以需要先进入到应用里面去,步骤如下 1. docker ps 找到对应的应用id,比如 zxc 2. 进入容器内部 docker exec -it 7690…

Elasticsearch:什么是搜索引擎?

搜索引擎定义 搜索引擎是一种软件程序或系统,旨在帮助用户查找存储在互联网或特定数据库中的信息。 搜索引擎的工作原理是对各种来源的内容进行索引和编目,然后根据用户的搜索查询向用户提供相关结果列表。 搜索引擎对于希望快速有效地查找特定信息的用…

怎么清理mac系统缓存系统垃圾文件 ?怎么清理mac系统DNS缓存

很多使用苹果电脑的用户都喜欢在同时运行多个软件,不过这样会导致在运行一些大型软件的时候出现不必要的卡顿现象,这时候我们就可以去清理下内存,不过很多人可能并不知道正确的清内存方式,下面就和小编一起来看看吧。 mac系统是一…

读十堂极简人工智能课笔记07_模拟与情感

1. 数码式考察 1.1. 制作计算机动画或游戏 1.1.1. 想怎么制作都可以 1.2. 计算机模拟 1.2.1. 目标是建造一个虚拟的实验室,其行为与现实完全一致,只是某些变量由我们来控制 1.3. 对现实世界进行建模并不容易,需要非常谨慎地收集和使用数…

Vscode vim 插件使用Ctrl+C和V进行复制粘贴到剪切板

Vscode vim 插件使用CtrlC和V进行复制粘贴到剪切板 使用这一个插件的时候复制粘贴和其他软件互动的时候体验不好, 并且不可以用Ctrl c, Ctrl v很不爽 "vim.commandLineModeKeyBindings": [{"before" : ["Ctrl", "c"],"after&q…

httpd apache

虚拟主机 配置环境 [rootlocalhost ~]#cd /var/www/html/ [rootlocalhost html]#mkdir 123 [rootlocalhost html]#mkdir abc [rootlocalhost html]#ls 123 abc [rootlocalhost html]#cd 123/ [rootlocalhost 123]#echo 123 > index.html [rootlocalhost 123]#cd ../abc/ […

区块链/加密币/敏感/特殊题材专供外媒发稿,英文多国语言海外新闻营销推广

【本篇由言同数字科技有限公司原创】敏感题材是海外媒体在报道过程中常遇到的难题,需要平衡新闻真实性、公正性与敏感性。本文将探讨海外媒体报道敏感题材所面临的挑战,并介绍如何抓住机遇提高报道质量。 第一部分:敏感题材报道的挑战 报道…

【Python】图像裁剪与匹配

图像裁剪与匹配 在计算机视觉领域,图像处理是一项关键的任务,其中图像裁剪和匹配是常见的操作之一。本文将介绍如何使用OpenCV库进行图像裁剪与匹配,并展示一个简单的示例代码。 1. 引言 在图像处理中,有时需要从一张大图中截取…

泰山派摄像头使用-opencv流程

1. 泰山派添加camera 连接摄像头连接到usb接口,查看dev设备: # 在终端中输入如下命令,可以查看到camera设备资源: ls /dev/video* 检查板卡上的camera设备资源示例 也可以使用v4l2命令查看 v4l2-ctl --list-devices v4l2-ctl --list-devices是一个命令…

CentOS上如何配置手动和定时任务自动进行时间同步

场景 Linux(Centos)上使用crontab实现定时任务(定时执行脚本): Linux(Centos)上使用crontab实现定时任务(定时执行脚本)_centos 定时任务-CSDN博客 Winserver上如何配置和开启NTP客户端进行时间同步: Winserver上如何配置和开启NTP客户端进行时间同步…

ADS-B Receiver Module TT-SC1 for UAV and Drones

目录 Introduction Applications Main features Technical parameters Basic technical information Electrical specification Recommended operation conditions General electrical parameters Introduction TT-SC1 is a high quality and low price OEM ADS-B…

使用【Python+Appium】实现自动化测试

一、环境准备 1.脚本语言:Python3.x IDE:安装Pycharm 2.安装Java JDK 、Android SDK 3.adb环境,path添加E:\Software\Android_SDK\platform-tools 4.安装Appium for windows,官网地址 Redirecting 点击下载按钮会到GitHub的…

java面试题之redis篇

1.redis 中的数据类型有哪些 随着 Redis 版本的更新,后面又支持了四种数据类型: BitMap(2.2 版新增)、HyperLogLog(2.8 版新增)、GEO(3.2 版新增)、Stream(5.0 版新增&am…