聊聊Golang饱受争议的Error

一、error是什么?在C中,返回错误通过errno.h中的错误代码来表示,比如0代表No error , 也就是没有错误;2代表No such file or directory,也就是找不到指定路径的文件或文件夹;5代表Input/Output error,表示输入或输出出现了错误...
而在我们最爱的Golang中,有这样一个饱受争议的error类型,它不是一个整数,而是一个接口 。
package mAInimport ("errors""fmt")type name struct {error string}func (n *name) Error() string {return fmt.Sprintf("%s : ...", n.error)}func main() {err := judge(11)//err := judge(1)//err := judge(6)fmt.Println(err)}func judge(num int) error {if num > 5 && num < 10 {return errors.New("这个数字大于5了..")}if num >= 10 {return fmt.Errorf("%d大于或等于10了...", num)}return &name{error: "hello"}}这是三种可以作为error返回值的方式 。errors.New()创建出来的error类型其实是errorString结构体 。
// src/errors/errors.go// New returns an error that formats as the given text.// Each call to New returns a distinct error value even if the text is identical.func New(text string) error { return &errorString{text}}// errorString is a trivial implementation of error.type errorString struct { s string}func (e *errorString) Error() string { return e.s}所以我们创建的结构体name其实和errors.New()底层的形式是基本一样的 。
而使用的fmt.Errorf其实是先将字符串格式化然后再调用errors.New() 。
二、error引人争论的点在哪?在Go中,设计者从语言层面要求人们需要明确地处理遇到的错误,但是因此导致的问题也十分明显 , 使用Go语言编写的代码中err会到处都是,不过优秀的IDE——Goland能够解决这个问题 , 使用Goland能够将err!=nil这段判断和处理压缩 , 不再干扰代码的阅读 。
我本人是不太喜欢JAVA的try-catch机制,可能是不太会用,Go语言官方提到try-catch会让代码变得比较混乱,很多程序员会胡乱catch异常,导致错误处理比较冗长 。
而Go语言通过多返回值机制,让返回错误变得很简单 , 并且提供panic和error两种机制,感觉这种机制更有优势,也看起来更简洁 。
煎鱼大佬之前有博客谈到了Go社区中关于Go错误处理的新议题 , 大家想了解的可以看看:

  • Go 错误处理新思路?用左侧函数和表达式:https://juejin.cn/post/7102268049213882398
  • Go try 新提案靠谱吗?想简化错误处理了 https://juejin.cn/post/7157931922008571940
其实之前Go社区中出现过多种关于错误处理的新议题,但是都没有被采纳...
三、如何优雅的处理错误1. 避免处理“哨兵错误”,即Sentinel errors比如为了判断err == io.EOF就得引入io包,这是标准库的包还能接受 , 如果是第三方库的包,并且使用“哨兵错误”,很容易导致循环引用的问题 。
2. 避免使用error类型虽然这种错误比“哨兵错误”要好,它可以捕获更多关于错误的上下文信息,比如出错的行数等其他字段信息 。但是又不可避免地在定义错误和使用错误的包之间形成依赖关系 , 又容易导致循环引用的问题 。
3. 使用不透明的“黑盒错误”func f() error{sentence,err := say.Hello()if err != nil{return err}// ...}上面这种写法是不是我们经常会用到?这种情况下,我们只需要判断err是否为空 , 不为空,代表有错误,就直接返回错误,否则就继续执行后面的流程 。
作为程序执行者 , 你没有能力看到程序错误的内部信息,只能知道程序有错或者没有错误 。这种错误处理作为一种调试辅助手段还是不错的 。
4. 使用Warp和Cause第三方库Github.com/pkg/errors可以输出错误堆栈,并且使用起来很简单,大家可以了解一下 。
// Wrap annotates 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}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")}func main() {_, err := ReadConfig()if err != nil {fmt.Println(err)os.Exit(1)}}


推荐阅读