19 Error handling and Go go语言错误处理
Error handling and Go go语言错误处理
12 July 2011
Introduction
If you have written any Go code you have probably encountered the built-in error
type. Go code uses error
values to indicate an abnormal state. For example, the os.Open
function returns a non-nil error
value when it fails to open a file.
func Open(name string) (file *File, err error)
The following code uses os.Open
to open a file. If an error occurs it calls log.Fatal
to print the error message and stop.
f, err := os.Open("filename.ext")
if err != nil {
log.Fatal(err)
}
// do something with the open *File f
You can get a lot done in Go knowing just this about the error
type, but in this article we'll take a closer look at error
and discuss some good practices for error handling in Go.
The error type
The error
type is an interface type. An error
variable represents any value that can describe itself as a string. Here is the interface's declaration:
type error interface {
Error() string
}
The error
type, as with all built in types, is predeclared in the universe block.
The most commonly-used error
implementation is the errors package's unexported errorString
type.
// errorString is a trivial implementation of error.
type errorString struct {
s string
} func (e *errorString) Error() string {
return e.s
}
You can construct one of these values with the errors.New
function. It takes a string that it converts to an errors.errorString
and returns as an error
value.
// New returns an error that formats as the given text.
func New(text string) error {
return &errorString{text}
}
Here's how you might use errors.New
:
func Sqrt(f float64) (float64, error) {
if f < 0 {
return 0, errors.New("math: square root of negative number")
}
// implementation
}
A caller passing a negative argument to Sqrt
receives a non-nil error
value (whose concrete representation is an errors.errorString
value). The caller can access the error string ("math: square root of...") by calling the error
's Error
method, or by just printing it:
f, err := Sqrt(-1)
if err != nil {
fmt.Println(err)
}
The fmt package formats an error
value by calling its Error() string
method.
It is the error implementation's responsibility to summarize the context. The error returned by os.Open
formats as "open /etc/passwd: permission denied," not just "permission denied." The error returned by our Sqrt
is missing information about the invalid argument.
To add that information, a useful function is the fmt
package's Errorf
. It formats a string according to Printf
's rules and returns it as an error
created by errors.New
.
if f < 0 {
return 0, fmt.Errorf("math: square root of negative number %g", f)
}
In many cases fmt.Errorf
is good enough, but since error
is an interface, you can use arbitrary data structures as error values, to allow callers to inspect the details of the error.
For instance, our hypothetical callers might want to recover the invalid argument passed to Sqrt
. We can enable that by defining a new error implementation instead of using errors.errorString
:
type NegativeSqrtError float64 func (f NegativeSqrtError) Error() string {
return fmt.Sprintf("math: square root of negative number %g", float64(f))
}
A sophisticated caller can then use a type assertion to check for a NegativeSqrtError
and handle it specially, while callers that just pass the error to fmt.Println
or log.Fatal
will see no change in behavior.
As another example, the json package specifies a SyntaxError
type that the json.Decode
function returns when it encounters a syntax error parsing a JSON blob.
type SyntaxError struct {
msg string // description of error
Offset int64 // error occurred after reading Offset bytes
} func (e *SyntaxError) Error() string { return e.msg }
The Offset
field isn't even shown in the default formatting of the error, but callers can use it to add file and line information to their error messages:
if err := dec.Decode(&val); err != nil {
if serr, ok := err.(*json.SyntaxError); ok {
line, col := findLine(f, serr.Offset)
return fmt.Errorf("%s:%d:%d: %v", f.Name(), line, col, err)
}
return err
}
(This is a slightly simplified version of some actual code from the Camlistore project.)
The error
interface requires only a Error
method; specific error implementations might have additional methods. For instance, the net package returns errors of type error
, following the usual convention, but some of the error implementations have additional methods defined by the net.Error
interface:
package net type Error interface {
error
Timeout() bool // Is the error a timeout?
Temporary() bool // Is the error temporary?
}
Client code can test for a net.Error
with a type assertion and then distinguish transient network errors from permanent ones. For instance, a web crawler might sleep and retry when it encounters a temporary error and give up otherwise.
if nerr, ok := err.(net.Error); ok && nerr.Temporary() {
time.Sleep(1e9)
continue
}
if err != nil {
log.Fatal(err)
}
Simplifying repetitive error handling
In Go, error handling is important. The language's design and conventions encourage you to explicitly check for errors where they occur (as distinct from the convention in other languages of throwing exceptions and sometimes catching them). In some cases this makes Go code verbose, but fortunately there are some techniques you can use to minimize repetitive error handling.
Consider an App Engine application with an HTTP handler that retrieves a record from the datastore and formats it with a template.
func init() {
http.HandleFunc("/view", viewRecord)
} func viewRecord(w http.ResponseWriter, r *http.Request) {
c := appengine.NewContext(r)
key := datastore.NewKey(c, "Record", r.FormValue("id"), 0, nil)
record := new(Record)
if err := datastore.Get(c, key, record); err != nil {
http.Error(w, err.Error(), 500)
return
}
if err := viewTemplate.Execute(w, record); err != nil {
http.Error(w, err.Error(), 500)
}
}
This function handles errors returned by the datastore.Get
function and viewTemplate
's Execute
method. In both cases, it presents a simple error message to the user with the HTTP status code 500 ("Internal Server Error"). This looks like a manageable amount of code, but add some more HTTP handlers and you quickly end up with many copies of identical error handling code.
To reduce the repetition we can define our own HTTP appHandler
type that includes an error
return value:
type appHandler func(http.ResponseWriter, *http.Request) error
Then we can change our viewRecord
function to return errors:
func viewRecord(w http.ResponseWriter, r *http.Request) error {
c := appengine.NewContext(r)
key := datastore.NewKey(c, "Record", r.FormValue("id"), 0, nil)
record := new(Record)
if err := datastore.Get(c, key, record); err != nil {
return err
}
return viewTemplate.Execute(w, record)
}
This is simpler than the original version, but the http package doesn't understand functions that return error
. To fix this we can implement the http.Handler
interface's ServeHTTP
method on appHandler
:
func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if err := fn(w, r); err != nil {
http.Error(w, err.Error(), 500)
}
}
The ServeHTTP
method calls the appHandler
function and displays the returned error (if any) to the user. Notice that the method's receiver, fn
, is a function. (Go can do that!) The method invokes the function by calling the receiver in the expression fn(w, r)
.
Now when registering viewRecord
with the http package we use the Handle
function (instead of HandleFunc
) as appHandler
is an http.Handler
(not an http.HandlerFunc
).
func init() {
http.Handle("/view", appHandler(viewRecord))
}
With this basic error handling infrastructure in place, we can make it more user friendly. Rather than just displaying the error string, it would be better to give the user a simple error message with an appropriate HTTP status code, while logging the full error to the App Engine developer console for debugging purposes.
To do this we create an appError
struct containing an error
and some other fields:
type appError struct {
Error error
Message string
Code int
}
Next we modify the appHandler type to return *appError
values:
type appHandler func(http.ResponseWriter, *http.Request) *appError
(It's usually a mistake to pass back the concrete type of an error rather than error
, for reasons discussed in the Go FAQ, but it's the right thing to do here because ServeHTTP
is the only place that sees the value and uses its contents.)
And make appHandler
's ServeHTTP
method display the appError
's Message
to the user with the correct HTTP status Code
and log the full Error
to the developer console:
func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if e := fn(w, r); e != nil { // e is *appError, not os.Error.
c := appengine.NewContext(r)
c.Errorf("%v", e.Error)
http.Error(w, e.Message, e.Code)
}
}
Finally, we update viewRecord
to the new function signature and have it return more context when it encounters an error:
func viewRecord(w http.ResponseWriter, r *http.Request) *appError {
c := appengine.NewContext(r)
key := datastore.NewKey(c, "Record", r.FormValue("id"), 0, nil)
record := new(Record)
if err := datastore.Get(c, key, record); err != nil {
return &appError{err, "Record not found", 404}
}
if err := viewTemplate.Execute(w, record); err != nil {
return &appError{err, "Can't display record", 500}
}
return nil
}
This version of viewRecord
is the same length as the original, but now each of those lines has specific meaning and we are providing a friendlier user experience.
It doesn't end there; we can further improve the error handling in our application. Some ideas:
- give the error handler a pretty HTML template,
- make debugging easier by writing the stack trace to the HTTP response when the user is an administrator,
- write a constructor function for
appError
that stores the stack trace for easier debugging,
- recover from panics inside the
appHandler
, logging the error to the console as "Critical," while telling the user "a serious error has occurred." This is a nice touch to avoid exposing the user to inscrutable error messages caused by programming errors. See the Defer, Panic, and Recover article for more details.
Conclusion
Proper error handling is an essential requirement of good software. By employing the techniques described in this post you should be able to write more reliable and succinct Go code.
By Andrew Gerrand
Related articles
- HTTP/2 Server Push
- Introducing HTTP Tracing
- Generating code
- Introducing the Go Race Detector
- Go maps in action
- go fmt your code
- Organizing Go code
- Debugging Go programs with the GNU Debugger
- The Go image/draw package
- The Go image package
- The Laws of Reflection
- "First Class Functions in Go"
- Profiling Go Programs
- A GIF decoder: an exercise in Go interfaces
- Introducing Gofix
- Godoc: documenting Go code
- Gobs of data
- C? Go? Cgo!
- JSON and Go
- Go Slices: usage and internals
- Go Concurrency Patterns: Timing out, moving on
- Defer, Panic, and Recover
- Share Memory By Communicating
- JSON-RPC: a tale of interfaces
19 Error handling and Go go语言错误处理的更多相关文章
- InflateException:Bin file line #19:Error inflating class MyTextView
InflateException:Bin file line #19:Error inflating class MyTextView 一.错误简介 为了实现TextView的跑马灯效果,我自己写了一 ...
- VS 报cmath(19): error C2061: 语法错误: 标识符“acosf” 错误
这是因为我在.c文件中用了 #include <iostream> using namespace std; 这样编译的时候就报: 出现错误类型如下:1>c:\program fil ...
- Erlang error handling
Erlang error handling Contents Preface try-catch Process link Erlang-way error handling OTP supervis ...
- setjmp()、longjmp() Linux Exception Handling/Error Handling、no-local goto
目录 . 应用场景 . Use Case Code Analysis . 和setjmp.longjmp有关的glibc and eglibc 2.5, 2.7, 2.13 - Buffer Over ...
- [转贴]从零开始学C++之异常(一):C语言错误处理方法、C++异常处理方法(throw, try, catch)简介
一.C语言错误处理方法 1.返回值(if … else语句判断错误) 2.errno(linux 系统调用) 3.goto语句(函数内局部跳转) 4.setjmp.longjmp(Do not use ...
- 从零开始学C++之异常(一):C语言错误处理方法、C++异常处理方法(throw, try, catch)简介
一.C语言错误处理方法 1.返回值(if … else语句判断错误) 2.errno(linux 系统调用) 3.goto语句(函数内局部跳转) 4.setjmp.longjmp(Do not use ...
- C语言错误处理方法、C++异常处理方法(throw, try, catch)简介
一.C语言错误处理方法 1.返回值(if … else语句判断错误) 2.errno(linux 系统调用) 3.goto语句(函数内局部跳转) 4.setjmp.longjmp(Do not use ...
- Fortify漏洞之Portability Flaw: File Separator 和 Poor Error Handling: Return Inside Finally
继续对Fortify的漏洞进行总结,本篇主要针对 Portability Flaw: File Separator 和 Poor Error Handling: Return Inside Fina ...
- MySQL Error Handling in Stored Procedures 2
Summary: this tutorial shows you how to use MySQL handler to handle exceptions or errors encountered ...
随机推荐
- 【BZOJ2724】蒲公英(分块)
[BZOJ2724]蒲公英(分块) 题面 洛谷 谴责权限题的行为 题解 分块什么的都不会,根本就没写过几次. 复杂度根本不会分析,吓得我赶快来练练. 这题要求的是区间众数,显然没有什么很好的主席树之类 ...
- 前端学习 --Css -- 子元素的伪类
:first-child 寻找父元素的第一个子元素,在所有的子元素中排序: :last-child 寻找父元素的最后一个子元素,在所有的子元素中排序: :nth-child 寻找父元素中的指定位置子元 ...
- 解题:USACO14MAR Sabotage
题面 题外话:我的实数二分有什么问题=.= 仍然(我为什么要这么说)是二分答案,如何检查呢?将所有的数减去二分出来的$mid$后求和得到和$sum$,然后如果在减出来的数列中能找出一段大于$sum$的 ...
- composer安装第三方库出现需要认证信息等原因
最近,在学习使用thinkcmf的时候,使用composer安装第三方类库,遇到了需要输入验证码的问题,援引https://laravel-china.org/topics/17893该链接中的讨论, ...
- Java入门:修改IDE主题颜色
1.去 http://eclipsecolorthemes.org/?view=theme&id=1下载你需要的颜色,根据id不同,配色方案不一样. 2.下载页面右侧的“Eclipse Pre ...
- 利用SHELL的PROMPT_COMMAND添加日志审计功能,实时记录任何用户的操作到日志文件中
利用 PROMPT_COMMAND 实现命令审计功能:记录什么用户,在什么时间,做了什么操作,然后将查到的信息记录到一个文件里. 具体操作: 将以下内容追加到/etc/profile: ####### ...
- etcd基本操作
目录 概述 安装etcd 使用etcdctl操作etcd 数据库操作 非数据库操作 使用curl操作etcd 概述 etcd是一个用于共享配置和服务的高可用键值存储系统,由CoreOS使用开发并作为C ...
- Lvs+Keepalived实现MySQL高可用
LVS+Keepalived+MySQL高可用配置 本文所有配置前提是已实现MySQL双主备份(MySQL双主) 安装前的准备: VIP:192.168.0.201 Keepalived: Keepa ...
- OpenStack安装部署(二)
中文文档:http://docs.openstack.org/mitaka/zh_CN/install-guide-rdo/提示:这个中文文档是直接翻译过来的,所以会有很多不通顺的地方. 服务介绍 M ...
- 1082 线段树练习 3 && 树状数组区间修改区间查询
1082 线段树练习 3 题意: 给定序列初值, 要求支持区间修改, 区间查询 Solution 用树状数组, 代码量小, 空间占用小 巧用增量数组, 修改时在 \(l\) 处 $ + val$ , ...