原文地址 https://blog.golang.org/errors-are-values

Go程序员之间(特别是这些刚接触Go语言的新人)一个常见的讨论点是如何处理错误。谈话经常变成为对如下代码序列出现次数的感叹。

If err!=nil{

return err

}

我们最近扫描了我们可以找到的所有开源项目,并发现这个代码段每页或每两页只出现一次,比你相信的要少。然而,如果这种看法仍然存在:所有时候都必须输入

If err!=nil

一定有些地方是错误的,明显的目标就是Go本身。

这是不幸的,误导的,很容易纠正的。也许正在发生的事情是新的程序员问道“如何处理错误?”,学习这种模式,并停止在那里。在其他语言中,可能会使用try-catch块或其他这样的机制来处理错误。因此,程序员认为,当我用旧的语言使用try-catch时,我将在Go中输入err!=nil。随着时间的推移,Go代码收集了很多这样的片段,结果感觉很笨拙。

无论这个解释是否合适,很明显,这些Go程序员错过了关于errors的基本观点:错误是值(Errors are values)

值可编程,并且由于错误是值,所以可以对错误进行编程。

当然,一个涉及错误值的常见语句是测试它是否为nil,但是还有无数的其他东西可以使用错误值,并且应用某些其他的东西可以使你的程序更好,消除了大量样板(如果每个error通过一个死记硬背的if语句来判断)。

这是一个简单的例子,来自bufio包的Scanner类型。它的Scan方法执行底层I/O,这当然可能导致错误。然而Scan方法根本不会显示错误。相反,它返回一个布尔值,并且在扫描结束时用一个单独的方法报告是否发生错误。客户端代码如下所示:

scanner := bufio.NewScanner(input)

for scanner.Scan() {

token := scanner.Text()

// process token

}

if err := scanner.Err(); err != nil {

// process the error

}

当然,这里有个对err的nil检查,但它似乎只执行一次。Scan方法可以被定义为

func (s *Scanner) Scan() (token []byte, error)

然后示例用户代码可能如下:(取决于如何取得token)

scanner := bufio.NewScanner(input)

for {

token, err := scanner.Scan()

if err != nil {

return err // or maybe break

}

// process token

}

这不是很大的不同,但有一个重要的区别。在此代码中,客户端必须在每次迭代时检查错误,但在实际的Scan API中,将错误处理从关键的API元素中抽象出来,在token上迭代。使用真实的API,客户端代码感觉更自然:循环直到完成,然后判断错误。错误处理不会干扰控制流。

看内部发生了什么,当然是,一旦Scan遇到I/O错误,它会记录它并返回false。一个单独的方法Err会在客户端询问时报告错误值。简而言之,这与把 if err != nil放到任何地方或要求客户端在每个token后检查错误的做法是不一样的。它是用错误值进行编程。简单的编程,是的,但仍然编程。

值得强调的是,无论设计如何,程序检查errors是非常重要的,然而它们是暴露出来的。这里的讨论不是关于如何避免检查错误,它是关于使用语言来优雅地处理错误。

当我在东京参加2014年秋天的GoCon时,出现了重复性错误检查代码的主题。一个热心的gopher,在Twitter上的@jxck_,回应了熟悉的关于错误检查的感叹。他有一些代码看起来像这样:

_, err = fd.Write(p0[a:b])

if err != nil {

return err

}

_, err = fd.Write(p1[c:d])

if err != nil {

return err

}

_, err = fd.Write(p2[e:f])

if err != nil {

return err

}

// and so on

这是非常重复的。在真正的代码中(更长的),有更多的,所以使用一个helper函数重构是不容易的,但是在这种理想的形式中,一个关于错误变量的函数将有帮助

var err error

write := func(buf []byte) {

if err != nil {

return

}

_, err = w.Write(buf)

}

write(p0[a:b])

write(p1[c:d])

write(p2[e:f])

// and so on

if err != nil {

return err

}

这个模式运作良好,但是需要在每个执行写入操作的函数中有一个闭包;一个单独的helper函数使用起来是笨拙的,因为err变量需要在调用之间保持。

我们可以通过借用Scan方法的想法来使这个更整洁,更通用和可重用。我在讨论中提到了这种技术,但是@jxck_没有看懂如何应用它。经过长时间的交流,收到语言障碍的阻碍,我问道我是否可以借用他的笔记本电脑,并通过键入一些代码来给他看。

我定义了一个名为errWrite的对象,如下所示:

type errWriter struct {

w   io.Writer

err error

}

并给它一个方法,write。它不需要具有标准的Write签名,小写部分来突出显示区别。write方法调用底层Writer的Write方法,并记录第一个错误以供将来参考:

func (ew *errWriter) write(buf []byte) {

if ew.err != nil {

return

}

_, ew.err = ew.w.Write(buf)

}

一旦发生错误,写入方法将变为无效,但是会保存错误值。

给出errWrite类型及其写入方法,可以重构以上代码:

ew:=&errWriter {w:fd}

ew.write(p0 [a:b])

ew.write(p1 [c:d])

ew.write(p2 [e:f])

// 等等

如果ew.err!= nil {

返回ew.err

}

这更干净,甚至与使用闭包相比,也使得实际的写入序列在页面上更容易看到。没有任何杂乱。使用错误值(和接口)编程使代码更好。

同一个软件包中的其他一些代码很可能会建立在这个想法上,甚至直接使用errWriter。

另外,一旦errWriter存在,还有更多的可以帮助,特别是在较少人为的例子中。它可以累加字节数。它可以将写入合并到单个缓冲区,然后可以原子传输。以及更多。

实际上,这种模式经常出现在标准库中。Archive/zip和net/http包使用它。这个讨论更为突出,bufio包的Writer实际上是一个errWriter想法的实现。虽然bufio.Writer.Write返回一个error,这主要是关于遵守io.Writer接口的形式。bufio.Writer的Write方法与上面的errWrite.write方法是一样,Flush报告错误,所以我们的例子可以这样写:

b := bufio.NewWriter(fd)

b.Write(p0[a:b])

b.Write(p1[c:d])

b.Write(p2[e:f])

// and so on

if b.Flush() != nil {

return b.Flush()

}

这种方法有一个明显的缺点,至少对于某些应用程序:无法知道错误发生之前完成了多少处理。如果这些信息很重要,则需要采用更细粒度的方法。通常,尽管如此,一个在末尾的完全或没有检查就足够了。

我们只看了一种避免重复错误处理代码的技术。请记住,使用errWrite或者bufio.Writer不是简化错误处理的唯一方法,而且这种方法不适用于所有情况。然而,重要的教训是errors是值,Go编程语言的全部功能可用于处理它们。

使用语言来简化错误处理。

但记住:无论你做什么,总是检查你的errors。

最后,关于我与@jxck_互动的完整故事,包括他录制的一点视频,请访问他的博客。

转载于:https://www.cnblogs.com/majianguo/p/6793152.html

Errors are values相关推荐

  1. CUDA 内存统一分析

    CUDA 内存统一分析 关于CUDA 编程的基本知识,如何编写一个简单的程序,在内存中分配两个可供 GPU 访问的数字数组,然后将它们加在 GPU 上. 本文介绍内存统一,这使得分配和访问系统中任何处 ...

  2. 使用Formik轻松开发更高质量的React表单(一)入门

    前言 发现Formik是在我学习redux-form过程中从国外一篇博客上偶然发现的,看到作者的高度肯定后我立即转到github上,正如许多朋友所关注的,Formik的星数达8282,这个数字在git ...

  3. redux-form的学习笔记二--实现表单的同步验证

    (注:这篇博客参考自redux-form的官方英文文档)左转http://redux-form.com/6.5.0/examples/syncValidation/ 在这篇博客里,我将用redux-f ...

  4. 使用Formik轻松开发更高质量的React表单(二)使用指南

    一个基本的例子 设想你要开发一个可以编辑用户数据的表单.不过,你的用户API端使用了具有类似下面的嵌套对象表达: {id: string,email: string,social: {facebook ...

  5. Go 开发关键技术指南 | 敢问路在何方?(内含超全知识大图)

    作者 | 杨成立(忘篱) 阿里巴巴高级技术专家 Go 开发关键技术指南文章目录: 为什么你要选择 Go? Go 面向失败编程 带着服务器编程金刚经走进 2020 年 敢问路在何方? Go 开发指南大图 ...

  6. redux-form V.7.4.2学习笔记(六)表单同步校验技术

    一.客户端同步校验支持 redux-form V.7.4.2提供了两种方法可以为表单提供同步客户端校验支持. 第一种是为整个redux-form提供校验函数,该函数接受一个以表单中所有值组成的对象作为 ...

  7. [翻译]Feedback on the Go Challenge solutions

    第一次Go Challenge比赛,中国区只有3人参赛. 赛后收到邮件,是一个审阅者的反馈,"Feedback on the Go Challenge solutions",摘录如 ...

  8. Practical Go: Real world advice for writing maintainable Go programs

    转载地址:Practical Go: Real world advice for writing maintainable Go programs Table of Contents Introduc ...

  9. Go error 处理实践

    文章转自:Go error 处理最佳实践 Go error 处理最佳实践 今天分享 go 语言 error 处理的最佳实践,了解当前 error 的缺点.妥协以及使用时注意事项.文章内容较长,干货也多 ...

最新文章

  1. ML之NB:利用朴素贝叶斯NB算法(CountVectorizer+不去除停用词)对fetch_20newsgroups数据集(20类新闻文本)进行分类预测、评估
  2. idea 将普通Java项目打包成可直接执行的jar
  3. SS CMS 全新跨平台 V7.0 版本正式发布
  4. 微服务中的异步消息通讯
  5. ffmpeg编译android,FFMPEG Android(2) 编译编译静态ffmpeg可执行文件
  6. 修改网页代码_网站建设中使用简洁规范代码的重要性
  7. xbanner 动画特效设置android,Axure教程:如何实现爱彼迎App首页Banner的切换效果
  8. php 特殊字符¥,php正则怎么替换符号
  9. python 传奇服务端_传奇游戏服务器源码学习
  10. 隐藏在SWT、Swing、AWT背后的故事
  11. 帮助两家基金公司运营蚂蚁财富号以后,PINTEC总结了一些经验
  12. 2021-08-24XLNET 语言模型
  13. 拼手速!兄弟连高洛峰细说php全套教学视频教程云盘下载
  14. java经典sql笔试题
  15. 世嘉MD游戏开发【一】:win10环境下配置SGDK
  16. QT5.14.2 + MSVC2017_64 + MySQL5.7.29 数据库驱动编译及配置
  17. 蓝海灵豚医疗器械信息管理系统B/S
  18. 在Win10与Ubuntu双系统中删除Ubuntu
  19. 软件公司 sun公司 Oracle公司
  20. python使用loaddata_Python data.load_data方法代码示例

热门文章

  1. maven3.5.0在win10中的安装及环境变量配置
  2. map遍历react中img图片路径出错
  3. linux命令之find和locate
  4. 100题_22 整数的二进制表示中1的个数
  5. 一个有关ajax去获取天气预报然后用echarts展现出来的小demo
  6. 剑指offer 面试64题
  7. div中的内容水平垂直居中
  8. Web Service学习笔记
  9. 改变listview的每个item的背景色
  10. 算法学习的链接(持续更新)