当在外层调用 ReadFile 函数时:
- func main() {
- _, err := ReadConfig()
- if err != nil {
- fmt.Println(err)
- os.Exit(1)
- }
- }
- func ReadConfig() ([]byte, error) {
- home := os.Getenv("HOME")
- config, err := ReadFile(filepath.Join(home, ".settings.xml"))
- return config, errors.Wrap(err, "could not read config")
- }
这样我们在 main 函数里就能打印出这样一个错误信息:
- could not read config: open failed: open /Users/dfc/.settings.xml: no such file or directory
它是有层次的,非常清晰。而如果我们用 pkg/errors 库提供的打印函数:
- func main() {
- _, err := ReadConfig()
- if err != nil {
- errors.Print(err)
- os.Exit(1)
- }
- }
能得到更有层次、更详细的错误:
- readfile.go:27: could not read config
- readfile.go:14: open failed
- open /Users/dfc/.settings.xml: no such file or directory
上面讲的是 Wrap 函数,接下来看一下 “Cause” 函数,以前面提到的 temporary 接口为例:
- type temporary interface {
- Temporary() bool
- }
- // IsTemporary returns true if err is temporary.
- func IsTemporary(err error) bool {
- te, ok := errors.Cause(err).(temporary)
- return ok && te.Temporary()
- }
判断之前先使用 Cause 取出错误,做断言,最后,递归地调用 Temporary 函数。如果错误没实现 temporary 接口,就会断言失败,返回 false。
Only handle errors once
什么叫“处理”错误:
- Handling an error means inspecting the error value, and making a decision.
意思是查看了一下错误,并且做出一个决定。
例如,如果不做任何决定,相当于忽略了错误:
- func Write(w io.Writer, buf []byte) { w.Write(buf)
- w.Write(buf)
- }
w.Write(buf) 会返回两个结果,一个表示写成功的字节数,一个是 error,上面的例子中没有对这两个返回值做任何处理。
下面这个例子却又处理了两次错误:
- func Write(w io.Writer, buf []byte) error {
- _, err := w.Write(buf)
- if err != nil {
- // annotated error goes to log file
- log.Println("unable to write:", err)
-
- // unannotated error returned to caller return err
- return err
- }
- return nil
- }
第一次处理是将错误写进了日志,第二次处理则是将错误返回给上层调用者。而调用者也可能将错误写进日志或是继续返回给上层。
这样一来,日志文件中会有很多重复的错误描述,并且在最上层调用者(如 main 函数)看来,它拿到的错误却还是最底层函数返回的 error,没有任何上下文信息。
(编辑:ASP站长网)
|