【Go语言最佳实践】通过消除错误来消除错误处理

你知道有什么比改进错误处理的语法更好吗?那就是根本不需要处理错误。

注意:
我不是说“删除你的错误处理”。我的建议是,修改你的代码,这样就不用处理错误了。

本节从 John Ousterhout 最近的著作“软件设计哲学”[9]中汲取灵感。该书的其中一章是“定义不存在的错误”。我们将尝试将此建议应用于 Go 语言。

计算行数

让我们编写一个函数来计算文件中的行数。

func CountLines(r io.Reader) (int, error) {var (br    = bufio.NewReader(r)lines interr   error)for {_, err = br.ReadString('\n')lines++if err != nil {break}}if err != io.EOF {return 0, err}return lines, nil
}

由于我们遵循前面部分的建议,CountLines 需要一个 io.Reader,而不是一个 *File;它的任务是调用者为我们想要计算的内容提供 io.Reader

我们构造一个 bufio.Reader,然后在一个循环中调用 ReadString 方法,递增计数器直到我们到达文件的末尾,然后我们返回读取的行数。

至少这是我们想要编写的代码,但是这个函数由于需要错误处理而变得更加复杂。 例如,有这样一个奇怪的结构:

_, err = br.ReadString('\n')
lines++
if err != nil {break
}

我们在检查错误之前增加了行数,这样做看起来很奇怪。

我们必须以这种方式编写它的原因是,如果在遇到换行符之前就读到文件结束,则 ReadString 将返回错误。如果文件中没有换行符,同样会出现这种情况。

为了解决这个问题,我们重新排列逻辑增来加行数,然后查看是否需要退出循环。

注意:
这个逻辑仍然不完美,你能发现错误吗?

但是我们还没有完成检查错误。当 ReadString 到达文件末尾时,预期它会返回 io.EOFReadString 需要某种方式在没有什么可读时来停止。因此,在我们将错误返回给 CountLine 的调用者之前,我们需要检查错误是否是 io.EOF,如果不是将其错误返回,否则我们返回 nil 说一切正常。

我认为这是 Russ Cox 观察到错误处理可能会模糊函数操作的一个很好的例子。我们来看一个改进的版本。

func CountLines(r io.Reader) (int, error) {sc := bufio.NewScanner(r)lines := 0for sc.Scan() {lines++}return lines, sc.Err()
}

这个改进的版本从 bufio.Reader 切换到 bufio.Scanner

bufio.Scanner 内部使用 bufio.Reader,但它添加了一个很好的抽象层,它有助于通过隐藏 CountLines 的操作来消除错误处理。

注意:
bufio.Scanner 可以扫描任何模式,但默认情况下它会查找换行符。

如果扫描程序匹配了一行文本并且没有遇到错误,则 sc.Scan() 方法返回 true 。因此,只有当扫描仪的缓冲区中有一行文本时,才会调用 for 循环的主体。这意味着我们修改后的 CountLines 正确处理没有换行符的情况,并且还处理文件为空的情况。

其次,当 sc.Scan 在遇到错误时返回 false,我们的 for 循环将在到达文件结尾或遇到错误时退出。bufio.Scanner 类型会记住遇到的第一个错误,一旦我们使用 sc.Err() 方法退出循环,我们就可以获取该错误。

最后, sc.Err() 负责处理 io.EOF 并在达到文件末尾时将其转换为 nil,而不会遇到其他错误。

贴士:
当遇到难以忍受的错误处理时,请尝试将某些操作提取到辅助程序类型中。

WriteResponse

我的第二个例子受到了 Errors are values 博客文章[10]的启发。

在本章前面我们已经看过处理打开、写入和关闭文件的示例。错误处理是存在的,但是接收范围内的,因为操作可以封装在诸如 ioutil.ReadFileioutil.WriteFile 之类的辅助程序中。但是,在处理底层网络协议时,有必要使用 I/O 原始的错误处理来直接构建响应,这样就可能会变得重复。看一下构建 HTTP 响应的 HTTP 服务器的这个片段。

type Header struct {Key, Value string
}type Status struct {Code   intReason string
}func WriteResponse(w io.Writer, st Status, headers []Header, body io.Reader) error {_, err := fmt.Fprintf(w, "HTTP/1.1 %d %s\r\n", st.Code, st.Reason)if err != nil {return err}for _, h := range headers {_, err := fmt.Fprintf(w, "%s: %s\r\n", h.Key, h.Value)if err != nil {return err}}if _, err := fmt.Fprint(w, "\r\n"); err != nil {return err}_, err = io.Copy(w, body)return err
}

首先,我们使用 fmt.Fprintf 构造状态码并检查错误。 然后对于每个标题,我们写入键值对,每次都检查错误。 最后,我们使用额外的 \r\n 终止标题部分,检查错误之后将响应主体复制到客户端。 最后,虽然我们不需要检查 io.Copy 中的错误,但我们需要将 io.Copy 返回的两个返回值形式转换为 WriteResponse 的单个返回值。

这里很多重复性的工作。 我们可以通过引入一个包装器类型 errWriter 来使其更容易。

errWriter 实现 io.Writer 接口,因此可用于包装现有的 io.WritererrWriter 写入传递给其底层 writer,直到检测到错误。 从此时起,它会丢弃任何写入并返回先前的错误。

type errWriter struct {io.Writererr error
}func (e *errWriter) Write(buf []byte) (int, error) {if e.err != nil {return 0, e.err}var n intn, e.err = e.Writer.Write(buf)return n, nil
}func WriteResponse(w io.Writer, st Status, headers []Header, body io.Reader) error {ew := &errWriter{Writer: w}fmt.Fprintf(ew, "HTTP/1.1 %d %s\r\n", st.Code, st.Reason)for _, h := range headers {fmt.Fprintf(ew, "%s: %s\r\n", h.Key, h.Value)}fmt.Fprint(ew, "\r\n")io.Copy(ew, body)return ew.err
}

errWriter 应用于 WriteResponse 可以显着提高代码的清晰度。 每个操作不再需要自己做错误检查。 通过检查 ew.err 字段,将错误报告移动到函数末尾,从而避免转换从 io.Copy 的两个返回值。

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

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

相关文章

springboot快鞋屋系统-计算机毕业设计源码06912

摘 要 如今,人们的消费水平和购物能力都大大提升,加上互联网技术日新月异的发展,电子商务这种新兴的商业模式,在短短几年里已经开始融入中国社会,成为家哈户晓的热门话题,在网上购物已经成为一种潮流&#…

键盘输入4个数,从小到大排序

题目 键盘输入4个整数&#xff0c;从小到大排序 思路 代码 #define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h>//键盘输入4个整数&#xff0c;从小到大排序 int main() {int n1, n2, n3, n4;scanf_s("%d %d %d %d", &n1, &n2, &n3, &n4);…

基于Java开发的个人视频网站的搭建与实现[附源码]

基于Java开发的个人视频网站的搭建与实现[附源码] &#x1f345; 作者主页 央顺技术团队 &#x1f345; 欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; &#x1f345; 文末获取源码联系方式 &#x1f4dd; &#x1f345; 查看下方微信号获取联系方式 承接各种定制系统 &am…

网工内推 | 外企网工、售前,最高15K*13薪,厂商认证优先

01 广州群欣软管有限公司 招聘岗位&#xff1a;网络工程师 职责描述&#xff1a; 1、负责公司计算机及办公设备的软、硬件日常维护&#xff1b; 2、负责公司erp系统、监控系统及网站的管理维护&#xff1b; 3、负责根据公司发展战略方向及业务规划&#xff0c;制定公司信息化的…

git checkout 某个分支后如何回退到执行之前的分支

在 Git 中&#xff0c;你可以使用 git checkout - 命令将工作目录切换回之前所在的分支。这个命令会将你的工作目录切换回上一个分支&#xff0c;就好像你执行了 git checkout 切换到上一个分支一样。 以下是操作步骤&#xff1a; 在命令行中执行 git checkout -。 这将会将你…

【Python优化算法篇】用于优化问题的scipy.optimize

Python优化算法篇 scipy.optimize Scipy是一个用于数学、科学和工程的开源库&#xff0c;它建立在NumPy的基础上&#xff0c;提供了一系列强大的科学计算工具。在Scipy中&#xff0c;优化模块&#xff08;scipy.optimize&#xff09;提供了多种优化算法&#xff0c;用于求解最小…

ST32——点亮第一盏灯

环境搭建 项目模板搭建 main函数 电路图 代码示例 #include "gd32f4xx.h" #include "systick.h" #include <stdio.h> #include "main.h"/** LED硬件对应关系 LED1 PE3 LED2 PD7 LED3 PG3 LED4 PA5点亮LED1 **/int main(void) {systick_…

【Linux】---Linux下基本指令(2)

目录 一、指令详细介绍1.1 cat 指令1.2 echo 指令1.3 more 指令1.4 less 指令1.5 head 指令1.6 tail 指令1.7 date 指令1.8 cal 指令1.9 find 指令1.10 grep 指令1.11 zip/unzip 指令1.12 tar 指令1.13 uname –r 指令&#xff1a; 一、指令详细介绍 1.1 cat 指令 语法&#…

每日汇评:黄金有望在复苏之路上重新夺回关键的2025美元关口

周一&#xff0c;金价在本周初延续了其复苏模式&#xff1b; 随着投资者重新评估美联储降息押注&#xff0c;美元跟随美债收益率走软&#xff1b; 黄金买家需要突破21日移动均线2025美元,RSI指数稳定在50以下&#xff1b; 随着买家将上周的复苏模式延续到周一&#xff0c;黄金价…

三防工业平板丨亿道加固平板定制丨三防平板电脑丨提升后勤管理

企业的后勤管理对于运作高效的商业模式至关重要。随着科技的不断发展&#xff0c;加固平板成为提升企业后勤水平的一项关键措施。本文将探讨加固平板在企业后勤管理中的应用和优势&#xff0c;并阐述如何利用这一技术提升企业的运营效率和竞争力。 一、三防加固平板的定义和功能…

云手机受欢迎背后的原因及未来展望

随着办公模式的演变&#xff0c;云手机的热潮迅速兴起。在各种办公领域&#xff0c;云手机正展现出卓越的实际应用效果。近年来&#xff0c;跨境电商行业迎来了蓬勃发展&#xff0c;其与国内电商的差异不仅体现在整体环境上&#xff0c;更在具体的操作层面呈现出独特之处。海外…

【Java代码洁癖】NO.2 单元测试mock显式赋值,不能忍

反例 RunWith(MockitoJunitRunner.class) public class Test {Mockpublic SomeBean someBean new SomeBean(); } 正例 RunWith(MockitoJunitRunner.class) public class Test {Mockpublic SomeBean someBean ; } 解读 使用Mock注解的对象不应该被显式赋值&#xff0c;应当…

python----面向对象

这里写目录标题 面向对象思想类类的定义类名的定义类的构造函数的定义类的属性类的方法定义 继承语法关于构造函数问题 文件操作绝对路径相对路径pycharm获取绝对路径和相对路径文件读写读文件open&#xff08;&#xff09;read&#xff08;&#xff09;readline&#xff08;&a…

Python(九十三)函数的参数总结

❤️ 专栏简介&#xff1a;本专栏记录了我个人从零开始学习Python编程的过程。在这个专栏中&#xff0c;我将分享我在学习Python的过程中的学习笔记、学习路线以及各个知识点。 ☀️ 专栏适用人群 &#xff1a;本专栏适用于希望学习Python编程的初学者和有一定编程基础的人。无…

如何在Win11系统中使用ubuntu(WSL)终端编译 Rust 程序

第一步&#xff1a;安装 WSL 参考微软提供的官方指南 在管理员模式下打开 PowerShell 或 Windows 命令提示符&#xff0c;方法是右键单击并选择“以管理员身份运行”&#xff0c;输入 wsl --install 命令&#xff0c;然后重启计算机。 第二步&#xff1a;安装 ubuntu22.04 …

网站常见的反爬手段及反反爬思路

摘要:介绍常见的反爬手段和反反爬思路&#xff0c;内容详细具体&#xff0c;明晰解释每一步&#xff0c;非常适合小白和初学者学习&#xff01;&#xff01;&#xff01; 目录 一、明确几个概念 二、常见的反爬手段及反反爬思路 1、检测user-agent 2、ip 访问频率的限制 …

JavaScript和Java:两个不同世界的编程语言

在计算机编程的世界里&#xff0c;有两个看似相似但实际上截然不同的语言&#xff1a;JavaScript和Java。虽然它们在名字上有相似之处&#xff0c;但它们的历史、设计目标和用途却迥然不同。 Java的诞生 Java最初于1991年由Sun Microsystems&#xff08;现在是Oracle的一部分…

Stochastic Depth 原理与代码解析

paper&#xff1a;Deep Networks with Stochastic Depth official implementation&#xff1a;https://github.com/yueatsprograms/Stochastic_Depth third-party implementation&#xff1a;https://github.com/open-mmlab/mmcv/blob/main/mmcv/cnn/bricks/drop.py 存在的问…

【day01】每天三道 java后端面试题:JDK、JRE和JVM | 字节码 | ACID

文章目录 1. JDK, JRE, JVM分别是什么&#xff1f;有什么区别&#xff1f;2. 什么是字节码&#xff1f;采用字节码的最大好处是什么&#xff1f;3. 什么是数据库事务&#xff1f;讲一下事务的ACID特性。 1. JDK, JRE, JVM分别是什么&#xff1f;有什么区别&#xff1f; 答题思路…

深度解析 Transformer 模型:原理、应用与实践指南【收藏版】

深度解析 Transformer 模型&#xff1a;原理、应用与实践指南 1. Transformer 模型的背景与引言2. Transformer 模型的原理解析2.1 自注意力机制&#xff08;Self-Attention&#xff09;自注意力机制原理 2.2 多头注意力机制&#xff08;Multi-Head Attention&#xff09;多头注…