设为首页 加入收藏

TOP

Golang error 的突围(三)
2019-09-18 11:10:41 】 浏览:158
Tags:Golang error 突围
nnotates cause with a message. func Wrap(cause error, message string) error // Cause unwraps an annotated error. func Cause(err error) error

通过 Wrap 可以将一个错误,加上一个字符串,“包装”成一个新的错误;通过 Cause 则可以进行相反的操作,将里层的错误还原。

有了这两个函数,就方便很多:

func ReadFile(path string) ([]byte, error) {
    f, err := os.Open(path)
    if err != nil {
        return nil, errors.Wrap(err, "open failed")
    }
    defer f.Close()
    
    buf, err := ioutil.ReadAll(f)
    if err != nil {
        return nil, errors.Wrap(err, "read failed")
    }
    return buf, nil
}

这是一个读文件的函数,先尝试打开文件,如果出错,则返回一个附加上了 “open failed” 的错误信息;之后,尝试读文件,如果出错,则返回一个附加上了 “read failed” 的错误。

当在外层调用 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,没有任何上下文信息。

使用第三方的 error 包就可以比较完美的解决问题:

func Write(w io.Write, buf []byte) error {?
    _, err := w.Write(buf)?
    return errors.Wrap(err, "write failed")?
}

返回的错误,对于人和机器而言,都是友好的。

小结

这一部分主要讲了处理 error 的一些原则,引入了第三方的 errors 包,使得错误处理变得更加优雅。

作者最后给出了一些结论:

  1. errors 就像对外提供的 API 一样,需要认真对待。
  2. 将 errors 看成黑盒,判断它的行为,而不是类型。
  3. 尽量不要使用 sentinel errors。
  4. 使用第三方的错误包来包裹 error(errors.Wrap),使得它更好用。
  5. 使用 errors.Cause 来获取底层的错误。

胎死腹中的 try 提案

之前已经出现用 “check & handle” 关键字和 “try 内置函数”改进错误处理流程的提案,目前 try 内置函数的提案已经被官方提前拒绝,原因是社区里一边倒地反对声音。

关于这两个提案的具体内容见参考资料【check & handle】和【try 提案】。

go 1.13 的改进

有一些 Go 语言失败的尝试,比如 Go 1.5 引入的 vendor 和 internal 来管理包,最后被滥用而引发了很多问题。因此 Go 1.13 直接抛弃了 GOPATHvendor 特性,改用 module 来管理包。

柴大在《Go 语言十年而立,Go2 蓄势待发》一文中表示:

比如最近 Go 语言之父之一 Robert Griesemer 提交的通过 try 内置函数来简化错误处理就被否决了。失败的尝试是一个好的现象,它表示 Go 语言依然在一些新兴领域的尝试 —— Go 语言依然处于活跃期。

今年 9 月 3 号,Go 发布 1.13 版本,除了 module 特性转正之外,还改进了数字字面量。比较重要的还有 defer 性能提升 30%,将更多的对象从堆上移动到栈上以提升性能,等等。

还有一个重大的改进发生在 errors 标准库中。er

首页 上一页 1 2 3 4 5 下一页 尾页 3/5/5
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇golang基础语法 下一篇Go Modules使用教程

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目