The Go Blog

Errors are values

12 January 2015

A common point of discussion among Go programmers, especially those new to the language, is how to handle errors. The conversation often turns into a lament at the number of times the sequence

if err != nil {
return err
}

shows up. We recently scanned all the open source projects we could find and discovered that this snippet occurs only once per page or two, less often than some would have you believe. Still, if the perception persists that one must type

if err != nil

all the time, something must be wrong, and the obvious target is Go itself.

This is unfortunate, misleading, and easily corrected. Perhaps what is happening is that programmers new to Go ask, "How does one handle errors?", learn this pattern, and stop there. In other languages, one might use a try-catch block or other such mechanism to handle errors. Therefore, the programmer thinks, when I would have used a try-catch in my old language, I will just type if err != nil in Go. Over time the Go code collects many such snippets, and the result feels clumsy.

Regardless of whether this explanation fits, it is clear that these Go programmers miss a fundamental point about errors: Errors are values.

Values can be programmed, and since errors are values, errors can be programmed.

Of course a common statement involving an error value is to test whether it is nil, but there are countless other things one can do with an error value, and application of some of those other things can make your program better, eliminating much of the boilerplate that arises if every error is checked with a rote if statement.

Here's a simple example from the bufio package's Scanner type. Its Scan method performs the underlying I/O, which can of course lead to an error. Yet the Scan method does not expose an error at all. Instead, it returns a boolean, and a separate method, to be run at the end of the scan, reports whether an error occurred. Client code looks like this:

scanner := bufio.NewScanner(input)
for scanner.Scan() {
token := scanner.Text()
// process token
}
if err := scanner.Err(); err != nil {
// process the error
}

Sure, there is a nil check for an error, but it appears and executes only once. The Scan method could instead have been defined as

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

and then the example user code might be (depending on how the token is retrieved),

scanner := bufio.NewScanner(input)
for {
token, err := scanner.Scan()
if err != nil {
return err // or maybe break
}
// process token
}

This isn't very different, but there is one important distinction. In this code, the client must check for an error on every iteration, but in the real Scanner API, the error handling is abstracted away from the key API element, which is iterating over tokens. With the real API, the client's code therefore feels more natural: loop until done, then worry about errors. Error handling does not obscure the flow of control.

Under the covers what's happening, of course, is that as soon as Scan encounters an I/O error, it records it and returns false. A separate method, Err, reports the error value when the client asks. Trivial though this is, it's not the same as putting

if err != nil

everywhere or asking the client to check for an error after every token. It's programming with error values. Simple programming, yes, but programming nonetheless.

It's worth stressing that whatever the design, it's critical that the program check the errors however they are exposed. The discussion here is not about how to avoid checking errors, it's about using the language to handle errors with grace.

The topic of repetitive error-checking code arose when I attended the autumn 2014 GoCon in Tokyo. An enthusiastic gopher, who goes by @jxck_ on Twitter, echoed the familiar lament about error checking. He had some code that looked schematically like this:

_, 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

It is very repetitive. In the real code, which was longer, there is more going on so it's not easy to just refactor this using a helper function, but in this idealized form, a function literal closing over the error variable would help:

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
}

This pattern works well, but requires a closure in each function doing the writes; a separate helper function is clumsier to use because the err variable needs to be maintained across calls (try it).

We can make this cleaner, more general, and reusable by borrowing the idea from the Scan method above. I mentioned this technique in our discussion but @jxck_ didn't see how to apply it. After a long exchange, hampered somewhat by a language barrier, I asked if I could just borrow his laptop and show him by typing some code.

I defined an object called an errWriter, something like this:

type errWriter struct {
w io.Writer
err error
}

and gave it one method, write. It doesn't need to have the standard Write signature, and it's lower-cased in part to highlight the distinction. The write method calls the Write method of the underlying Writer and records the first error for future reference:

func (ew *errWriter) write(buf []byte) {
if ew.err != nil {
return
}
_, ew.err = ew.w.Write(buf)
}

As soon as an error occurs, the write method becomes a no-op but the error value is saved.

Given the errWriter type and its write method, the code above can be refactored:

ew := &errWriter{w: fd}
ew.write(p0[a:b])
ew.write(p1[c:d])
ew.write(p2[e:f])
// and so on
if ew.err != nil {
return ew.err
}

This is cleaner, even compared to the use of a closure, and also makes the actual sequence of writes being done easier to see on the page. There is no clutter any more. Programming with error values (and interfaces) has made the code nicer.

It's likely that some other piece of code in the same package can build on this idea, or even use errWriterdirectly.

Also, once errWriter exists, there's more it could do to help, especially in less artificial examples. It could accumulate the byte count. It could coalesce writes into a single buffer that can then be transmitted atomically. And much more.

In fact, this pattern appears often in the standard library. The archive/zip and net/http packages use it. More salient to this discussion, the bufio package's Writer is actually an implementation of the errWriter idea. Although bufio.Writer.Write returns an error, that is mostly about honoring the io.Writer interface. The Write method of bufio.Writer behaves just like our errWriter.write method above, with Flush reporting the error, so our example could be written like this:

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()
}

There is one significant drawback to this approach, at least for some applications: there is no way to know how much of the processing completed before the error occurred. If that information is important, a more fine-grained approach is necessary. Often, though, an all-or-nothing check at the end is sufficient.

We've looked at just one technique for avoiding repetitive error handling code. Keep in mind that the use of errWriter or bufio.Writer isn't the only way to simplify error handling, and this approach is not suitable for all situations. The key lesson, however, is that errors are values and the full power of the Go programming language is available for processing them.

Use the language to simplify your error handling.

But remember: Whatever you do, always check your errors!

Finally, for the full story of my interaction with @jxck_, including a little video he recorded, visit his blog.

By Rob Pike

参考资料:

https://blog.golang.org/errors-are-values

【GoLang】GoLang 官方 对 error 处理的意见的更多相关文章

  1. [golang]golang如何覆盖输出console,实现进度条;golang一个骚气的进度提示库

    [golang]golang如何覆盖输出console,实现进度条 package main import( "fmt" "os" "time&quo ...

  2. 【GoLang】GoLang 错误处理 -- 使用 error is value 的思路处理,检查并处理error

    吐血推荐: https://dave.cheney.net/2016/04/27/dont-just-check-errors-handle-them-gracefully 参考资料: https:/ ...

  3. GO语言(golang)官方网站!

    GO语言官方网站,在上面可以查看所有API文档.使用在线工具编写程序,你可以去看看!! https://golang.org/

  4. golang 用defer 捕获error 需小心

    有时一个函数内需要根据最后是否出错,决定是否执行某个操作.这时候如果函数的分支又比较多,就会比较麻烦了. defer 处理这个情况刚好合适 func main() { var err error by ...

  5. [golang]golang 汇编

    https://lrita.github.io/2017/12/12/golang-asm/#why 在某些场景下,我们需要进行一些特殊优化,因此我们可能需要用到golang汇编,golang汇编源于 ...

  6. [golang]golang time.After内存泄露问题分析

    无意中看到一篇文章说,当在for循环里使用select + time.After的组合时会产生内存泄露,于是进行了复现和验证,以此记录 内存泄露复现 问题复现测试代码如下所示: package mai ...

  7. [golang]Golang实现高并发的调度模型---MPG模式

    Golang实现高并发的调度模型---MPG模式 传统的并发形式:多线程共享内存,这也是Java.C#或者C++等语言中的多线程开发的常规方法,其实golang语言也支持这种传统模式,另外一种是Go语 ...

  8. [随笔][Golang][golang nil 相关]

    nil 是不能比较的 不同类型的nil的address是一样的 不同类型的nil是不能比较的 nil 是map, slice, pointer, channel, func, interface的零值 ...

  9. 如何加速golang写业务的开发速度

    如何加速golang写业务的开发速度 不要忌讳panic golang写业务代码经常会被吐槽,写业务太慢了,其中最大的吐槽点就是,处理各种error太麻烦了.一个项目中,会有30%或者更多的是在处理e ...

随机推荐

  1. 北斗/GPS

    北斗/GPS都是用的nmea通用协议. NMEA-0183协议定义的语句非常多,但是常用的或者说兼容性最广的语句只有$GPGGA.$GPGSA.$GPGSV.$GPRMC.$GPVTG.$GPGLL等 ...

  2. 捕获EF提交异常

    try { } catch (DbEntityValidationException dbex) { string errMsg = string.Empty; foreach (var eve in ...

  3. Ajax– 刷新页面 【转】

    jquery刷新页面(局部及全页面刷新) 2009-12-31 11:39:32|  分类: javascript|举报|字号 订阅 局部刷新: 这个方法就多了去了,常见的有以下几种: $.get方法 ...

  4. Orchard源码分析(1):Orchard架构

      本文主要参考官方文档"How Orchard works"以及Orchardch上的翻译.   源码分析应该做到庖丁解牛,而不是以管窥豹或瞎子摸象.所以先对Orchard架构有 ...

  5. 浅谈T-SQL中的子查询

    引言 这篇文章我们来简单的谈一下子查询的相关知识.子查询可以分为独立子查询和相关子查询.独立子查询不依赖于它所属的外部查询,而相关子查询则依赖于它所属的外部查询.子查询返回的值可以是标量(单值).多值 ...

  6. Yii2-Redis使用小记 - Cache(转)

    前些天简单学习了下 Redis,现在准备在项目上使用它了.我们目前用的是 Yii2 框架,在官网搜索了下 Redis,就发现了yii2-redis这扩展. 安装后使用超简单,打开 common/con ...

  7. apt-get方式安装lnmp环境

    安装nginxsudo apt-get install nginx安装php和mysqlsudo apt-get install php5-cli php5-cgi php5-curl php5-my ...

  8. Java UDP 数据报

    服务端,创建一个DatagramSocket对象,侦听在某个端口,创建一个DatagramPacket对象接受数据.收到客户端发来的信息后,有原封不动转发到客户端. public class Data ...

  9. AjaxAnywhere+struts用法

    AjaxAnywhere的用法 1,简介 AjaxAnywhere被设计成能够把任何一套现存的JSP组件转换成AJAX感知组件而不需要复杂的JavaScript编码.它利用标签把Web页面简单地划分成 ...

  10. linux下安装python环境

    1.linux下安装python3 a. 准备编译环境(环境如果不对的话,可能遇到各种问题,比如wget无法下载https链接的文件) yum groupinstall 'Development To ...