【GoLang】GoLang 错误处理 -- 使用 error is value 的思路处理,检查并处理error
吐血推荐:
https://dave.cheney.net/2016/04/27/dont-just-check-errors-handle-them-gracefully
参考资料:
https://blog.golang.org/errors-are-values
https://dave.cheney.net/2016/06/12/stack-traces-and-the-errors-package
https://godoc.org/github.com/pkg/errors#Cause
Stack traces and the errors package
A few months ago I gave a presentation on my philosophy for error handling. In the talk I introduced a small errors package designed to support the ideas presented in the talk.
This post is an update to my previous blog post which reflects the changes in the errorspackage as I’ve put it into service in my own projects.
Wrapping and stack traces
In my April presentation I gave examples of using the Wrap function to produce an annotated error that could be unwrapped for inspection, yet mirrored the recommendations from Kernighan and Donovan’s book.
package main import "fmt"
import "github.com/pkg/errors" func main() {
err := errors.New("error")
err = errors.Wrap(err, "open failed")
err = errors.Wrap(err, "read config failed") fmt.Println(err) // read config failed: open failed: error
}
Wraping an error added context to the underlying error and recorded the file and line that the error occurred. This file and line information could be retrieved via a helper function, Fprint, to give a trace of the execution path leading away from the error. More on that later.
However, when I came to integrate the errors package into my own projects, I found that using Wrap at each call site in the return path often felt redundant. For example:
func readconfig(file string) {
if err := openfile(file); err != nil {
return errors.Wrap(err, "read config failed")
}
// ...
}
If openfile failed it would likely annotate the error it returned with open failed
, and that error would also include the file and line of the openfile function. Similarly, readconfig‘s wrapped error would be annotated with read config failed
as well as the file and line of the call to errors.Wrapinside the readconfig function.
I realised that, at least in my own code, it is likely that the name of the function contains sufficient information to frequently make the additional context passed to Wrap redundant. But as Wrap requires a message, even if I had nothing useful to add, I’d still have to pass something:
if err != nil {
return errors.Wrap(err, "") // ewww
}
I briefly considered making Wrap variadic–to make the second parameter optional–before realising that rather than forcing the user to manually annotate each stack frame in the return path, I can just record the entire stack trace at the point that an error is created by the errorspackage.
I believe that for 90% of the use cases, this natural stack trace–that is the trace collected at the point New or Errorf are called–is correct with respect to the information required to investigate the error’s cause. In the other cases, Wrap and Wrapf can be used to add context when needed.
This lead to a large internal refactor of the package to collect and expose this natural stack trace.
Fprint and Print have been removed
As mentioned earlier, the mechanism for printing not just the err.Error() text of an error, but also its stack trace, has also changed with feedback from early users.
The first attempts were a pair of functions; Print(err error), which printed the detailed error to os.Stderr, and Fprint(w io.Writer, err error) which did the same but allowed the caller to control the destination. Neither were very popular.
Print was removed in version 0.4.0 because it was just a wrapper around Fprint(os.Stderr, err) and was hard to test, harder to write an example test for, and didn’t feel like its three lines paid their way. However, with Print gone, users were unhappy that Fprint required you to pass an io.Writer, usually a bytes.Buffer, just to retrieve a string form of the error’s trace.
So, Print and Fprint were the wrong API. They were too opinionated, without it being a useful opinion. Fprint has been slowly gutted over the period of 0.5, 0.6 and now has been replaced with a much more powerful facility inspired by Chris Hines’ go-stack/stack package.
The errors package now leverages the powerful fmt.Formatter interface to allow it to customise its output when any error generated, or wrapped by this package, is passed to fmt.Printf. This extended format is activated by the %+v verb. For example,
func main() {
err := parseArgs(os.Args[1:])
fmt.Printf("%v\n", err)
}
Prints, as expected,
not enough arguments, expected at least 3, got 0
However if we change the formatting verb to %+v,
func main() {
err := parseArgs(os.Args[1:])
fmt.Printf("%+v\n", err)
}
the same error value now results in
not enough arguments, expected at least 3, got 0
main.parseArgs
/home/dfc/src/github.com/pkg/errors/_examples/wrap/main.go:12
main.main
/home/dfc/src/github.com/pkg/errors/_examples/wrap/main.go:18
runtime.main
/home/dfc/go/src/runtime/proc.go:183
runtime.goexit
/home/dfc/go/src/runtime/asm_amd64.s:2059
For those that need more control the Cause and StackTrace behaviours return values who have their own fmt.Formatter implementations. The latter is alias for a slice of Frame values which represent each frame in a call stack. Again, Frame implements several fmt.Formatter verbs that allow its output to be customised as required.
Putting it all together
With the changes to the errors package, some guidelines on how to use the package are in order.
- In your own code, use
errors.Neworerrors.Errorfat the point an error occurs.func parseArgs(args []string) error {
if len(args) < 3 {
return errors.Errorf("not enough arguments, expected at least 3, got %d", len(args))
}
// ...
} - If you receive an error from another function, it is often sufficient to simply return it.
if err != nil {
return err
} - If you interact with a package from another repository, consider using
errors.Wraporerrors.Wrapfto establish a stack trace at that point. This advice also applies when interacting with the standard library.f, err := os.Open(path)
if err != nil {
return errors.Wrapf(err, "failed to open %q", path)
} - Always return errors to their caller rather than logging them throughout your program.
- At the top level of your program, or worker goroutine, use
%+vto print the error with sufficient detail.func main() {
err := app.Run()
if err != nil {
fmt.Printf("FATAL: %+v\n", err)
os.Exit(1)
}
} - If you want to exclude some classes of error from printing, use
errors.Causeto unwraperrors before inspecting them.
Conclusion
The errors package, from the point of view of the four package level functions, New, Errorf, Wrap, and Wrapf, is done. Their API signatures are well tested, and now this package has been integrated into over 100 other packages, are unlikely to change at this point.
The extended stack trace format, %+v, is still very new and I encourage you to try it and leave feedback via an issue.
This entry was posted in Go, Programming and tagged error handling, errors, stacktrace on June 12, 2016.
【GoLang】GoLang 错误处理 -- 使用 error is value 的思路处理,检查并处理error的更多相关文章
- [Golang]-4 错误处理、Panic、Defer
目录 错误和异常 案例 Panic Defer 使用 defer+recover 来处理错误 参考文章: Go 语言使用一个独立的·明确的返回值来传递错误信息的.这与使用异常的 Java 和 Ruby ...
- GoLang之错误处理
错误处理 error Go语言引入了一个错误处理的标准模式,即error接口,该接口定义如下: type error interface { Error() string } 对于大多数函数,如果要返 ...
- 『GoLang』错误处理
Go 没有像 Java 和 .NET 那样的 try/catch 异常机制:不能执行抛异常操作.但是有一套 defer-panic-and-recover 机制. Go 的设计者觉得 try/catc ...
- caffe编译环境的错误:..build_release/src/caffe/proto/caffe.pb.h:23:35: fatal error: google/protobuf/arena.h: 没有那个文件
在搭建caffe的环境时出现错误: .build_release/src/caffe/proto/caffe.pb.h:23:35: fatal error: google/protobuf/aren ...
- Android错误:can not get file data of lua/start_v2.op [LUA ERROR] [string "require "lua/start_v2””] 已解决
错误: can not get file data of lua/start_v2.op [LUA ERROR] [string "require "lua/start_v2””] ...
- [golang]golang如何覆盖输出console,实现进度条;golang一个骚气的进度提示库
[golang]golang如何覆盖输出console,实现进度条 package main import( "fmt" "os" "time&quo ...
- error adding symbols: DSO missing from command line collect2: error: ld returned 1 exit status
Windows服务器Azure云编译安装MariaDB教程 www.111cn.net 编辑:future 来源:转载 安装MariaDB数据库最多用于linux系统中了,下文给各位介绍在Window ...
- 新建SpringBoot项目运行页面报错Whitelabel Error Page This application has no explicit mapping for /error, so yo
新建SpringBoot项目运行页面报错Whitelabel Error Page This application has no explicit mapping for /error, so yo ...
- java virtual machine launcher Error:Could not create the Java Virtual Machine. Error:A Fatal exception has occurred,Program will exit.
Error:Could not create the Java Virtual Machine. Error:A Fatal exception has occurred,Program will e ...
随机推荐
- History(历史)命令用法
如果你经常使用 Linux 命令行,那么使用 history(历史)命令可以有效地提升你的效率.本文将通过实例的方式向你介绍 history 命令的用法. 使用 HISTTIMEFORMAT 显示时间 ...
- VC亲自教你写BP
2015年5月24日下午,腾讯开放平台“创业ABC”沙龙在腾讯众创空间(上海)举行.活动以“创业融资实战——从计划书到如何估值到如何花钱”为主题,险峰华兴投资负责人徐建海先生现场分享<如何写BP ...
- mysql 数据表中查找、删除重复记录
为了性能考虑,在阅读之前提醒大家,如果有子查询,子查询查询到的数据最好不要超过总数据量的30%. 查询有重复数据的记录 select * from F group by a,b,c,d having ...
- 存储过程中的when others then 和 raise
EXCEPTION when others then rollback; dbms_output.put_line('code:' || sqlcode); dbms_output.put_line( ...
- android自定义控件(3)-自定义当前按钮属性
那么还是针对我们之前写的自定义控件:开关按钮为例来说,在之前的基础上,我们来看看有哪些属性是可以自定义的:按钮的背景图片,按钮的滑块图片,和按钮的状态(是开还是关),实际上都应该是可以在xml文件中直 ...
- 【转】flume+kafka+zookeeper 日志收集平台的搭建
from:https://my.oschina.net/jastme/blog/600573 flume+kafka+zookeeper 日志收集平台的搭建 收藏 jastme 发表于 10个月前 阅 ...
- matlab 聚类
目前已知matlab的聚类方法有三种: 一.利用 clusterdata函数对样本数据进行一次聚类,其缺点为可供用户选择的面较窄,不能更改距离的计算方法: 二.层次聚类,该方法较为灵活,需要进行细节了 ...
- 用jinja做了个E-Letter小项目
做了一个html E-Letter项目. 邮件模板采用jinja2, html 邮件内容生成简直太爽了. 整个项目开发只用了2个小时, 调试却花了大半天时间, 生成的邮件总是发不出去. 于是, 打开 ...
- javascript 2048游戏
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- HDOJ 3507 Print Article
Print Article Time Limit: 9000/3000 MS (Java/Others) Memory Limit: 131072/65536 K (Java/Others)To ...