开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 20天,点击查看活动详情
前言
PHP处理程序异常错误是通过try/catch机制
来进行捕获的,到学Go的时候发现Go并没有这个机制,而是通过定义error接口类型
,来明确返回错误信息这个特殊值,从而进一步处理。
下面先快速过一遍error的基础知识,然后进一步学习如何优雅的处理error。
基础知识
error接口定义
type error interface {
Error() string
}
创建error
使用errors.New方法,输入一个错误信息文本,输出一个error接口类型,可使用err.Error获取错误文本
func main() {
err := errors.New("error here")
if err != nil {
fmt.Println(err) //这里底层会以err.Error()去执行,获取到错误文本并打印
return
}
fmt.Println("good")
}
//输出:
//error here
errors.New的实现
只要实现了Error方法,就可以认为是实现了这个error接口
package errors
// 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
}
fmt.Errorf包装error
使用%w
和%v
,进行包装错误。注意:输出的类型有所区别( %w是包装之后的error,%v是原始的error)
func main() {
err := errors.New("here is error")
if err != nil {
fmt.Println(err)
err2 := fmt.Errorf("format err:%w", err)
fmt.Printf("type: %T, val: %v", err2, err2) //type: *fmt.wrapError, val: format err:here is error
fmt.Println()
err3 := fmt.Errorf("format err:%v", err)
fmt.Printf("type: %T, val: %v", err3, err3) //type: *errors.errorString, val: format err:here is error
return
}
fmt.Println("good")
}
errors包的其他函数
针对包装过的error,erros包还提供了下面3个方法
func Unwrap(err error) error // 获得err包含下一层错误
func Is(err, target error) bool // 判断err是否包含target
func As(err error, target interface{}) bool // 判断err是否为target类型
优雅的处理error
Bad Case
遇到err就打日志,并直接把err一层一层往上抛,这样会导致最终在哪报的err都不明确。日志冗余的同时,排查问题的难度也被加大
func main() {
err := do1()
fmt.Println(err)
}
func do1() error {
err := do2()
if err != nil {
log.Printf("do1 err:%v", err)
return err
}
return nil
}
func do2() error {
_, err := os.OpenFile("./a.go", os.O_RDONLY, 0)
if err != nil {
log.Printf("do2 err:%v", err)
return err
}
return nil
}
//输出:
//2023/02/21 20:34:39 do2 err:open ./a.go: no such file or directory
//2023/02/21 20:34:39 do1 err:open ./a.go: no such file or directory
//open ./a.go: no such file or directory
Good Case
使用errros.Wrap
,做error的包装,也不需要每一步err都打日志,因为有明确的调用栈stack,能很明确的知道报错的地方以及操作链路
func main() {
err := do11()
fmt.Println(err)
fmt.Printf("origin error: %T %v\n\n", errors.Cause(err), errors.Cause(err))
fmt.Printf("stack trace: %+v\n", err)
}
func do11() error {
err := do12()
if err != nil {
return errors.Wrap(err, "do11 error")
}
return nil
}
func do12() error {
_, err := os.OpenFile("./a.go", os.O_RDONLY, 0)
if err != nil {
return errors.Wrap(err, "do12 error")
}
return nil
}
输出结果:
do11 error: do12 error: open ./a.go: no such file or directory
origin error: *fs.PathError open ./a.go: no such file or directory
stack trace: open ./a.go: no such file or directory
do12 error
main.do12
/usr/local/var/www_go/src2/learn-go/09_error/03_error_wrap.go:30
main.do11
/usr/local/var/www_go/src2/learn-go/09_error/03_error_wrap.go:20
main.main
/usr/local/var/www_go/src2/learn-go/09_error/03_error_wrap.go:12
runtime.main
/usr/local/go/src/runtime/proc.go:225
runtime.goexit
/usr/local/go/src/runtime/asm_amd64.s:1371
do11 error
main.do11
/usr/local/var/www_go/src2/learn-go/09_error/03_error_wrap.go:22
main.main
/usr/local/var/www_go/src2/learn-go/09_error/03_error_wrap.go:12
runtime.main
/usr/local/go/src/runtime/proc.go:225
runtime.goexit
/usr/local/go/src/runtime/asm_amd64.s:1371
总结
从PHP的try/catch机制到Go的error接口类型特殊值,对程序异常错误的处理,编码思维发生了彻底的变化。针对Go error如果优雅的处理,做了Bad Case和Good Case的对比,明确知道了使用errors.Wrap
写法能更加优雅。
如果本文对你有帮助,欢迎点赞收藏加关注,如果本文有错误的地方,欢迎指出!