有很多种方法来声明 errors:
errors.New
声明简单的静态字符串错误信息fmt.Errorf
声明格式化的字符串错误信息- 为自定义类型实现
Error()
方法 - 通过
"pkg/errors".Wrap
包装错误类型
1.如何自定义错误类型?
客户需要检测并处理此错误吗?如果是,那应该自定义类型,并实现 Error()
方法。
type errNotFound struct { } func (fe *errNotFound) Error() string { return "找不到文件" } //模拟错误 func openFile() ([]byte, error) { return nil, &errNotFound{} } func main() { _, err := openFile() if err != nil { fmt.Println(err) } } # 找不到文件
当你需要捕获一些错误的具体信息,往往也需要通过自定义的方式,比如下面这个例子捕获哪个文件打开错误了
type errNotFound struct { file string } func (e errNotFound) Error() string { return fmt.Sprintf("file %q not found", e.file) } func open(file string) error { return errNotFound{file: file} } func use() { if err := open("test.txt"); err != nil { if err, ok := err.(errNotFound); ok { fmt.Println(err) } else { panic("unknown error") } } } func main() { use() } # file "test.txt" not found
直接将自定义的错误类型设为导出需要特别小心,因为这意味着他们已经成为包的公开 API 的一部分了。更好的方式是暴露一个匹配函数来检测错误。
type errNotFound struct { file string } func (e errNotFound) Error() string { return fmt.Sprintf("file %q not found", e.file) } func IsNotFoundError(err error) bool { _, ok := err.(errNotFound) return ok } func Open(file string) error { return errNotFound{file: file} } // package bar if err := foo.Open("foo"); err != nil { if foo.IsNotFoundError(err) { // handle } else { panic("unknown error") } }
2.当你需要定义一个简单的错误信息,可以用errors.New也可以用fmt.Errorf
如果项目中仅仅是为了打日志,就可以直接用了,如果项目中需要捕获具体的错误并进行对应的处理,规范的做法是先声明这个错误类型
比较优秀的做法是这样做,切记不要通过Error()判断字符串这种方式去识别错误
var ErrCouldNotOpen = errors.New("could not open") func Open() error { return ErrCouldNotOpen } if err := foo.Open(); err != nil { if err == ErrCouldNotOpen { // handle } else { panic("unknown error") } }
errors.New本质上也可以通过自定义模拟实现
func New(text string) error { return &errorString{text} } type errorString struct { s string } func (e *errorString) Error() string { return e.s } func test() error { return New("测试NewError") } func main() { err := test() fmt.Printf("%v", err) } # 测试NewError
3. 实际项目中,大量的if err != nil { log.Error(...)....}充斥在代码中
一、可读性较差
二、大量重复日志,给运维带来压力
三、很难一次定位错误位置
可以通过 "
p
kg/e
rrors
"包中提供的 Wrap、WithMessage、Cause等方法解决这个问题
Wrap 可以包装信息加堆栈信息、WithMessage只包装信息、Cause可以找到原始错误
func inner() error { return errors.Wrap(sql.ErrNoRows, "inner failed") } func out() error { return errors.WithMessage(inner(), "out failed") } func main() { err := out() if err != nil { fmt.Printf("%+v\n", err) } # sql: no rows in result set # inner failed # ...test.go:11 # ...test.go:14 # ...test.go:17 # ... # out failed if errors.Cause(err) == sql.ErrNoRows { # true } }
参考:
https://qcrao.com/2019/09/18/golang-error-break-through/
https://zhuanlan.zhihu.com/p/98152645