持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第15天,点击查看活动详情
转GO语言有了差不多8个月的时间,之前是做java相关的工作,因为java语言是一门非常受欢迎的语言,他强大的生态和语言本身的魅力,让我们在编写代码的时候非常的舒适,比如java的异常机制,java的动态代理,接口,多态,继承等等特性使得我们在编码的时候非常的舒服,写出的代码也非常的优雅,规范,但是自从转了Go之后,发现Go语言很多东西都设计的非常的简单,如,Go的容器,List,Map等,最让我感到编程不舒服的就是GO的错误处理,经过一段时间的研究,发现了一个非常好用的Go错误处理的库,今天分享给大家~希望对大家的工作能有所帮助~~~
糟糕的代码
我们在编写业务代码的时候经常会有类似这种代码
if err != nil {
log.Error("err info: %v",err)
return err
}
如果我们编写了一个模板方法,那么一半的代码都是在进行错误处理,如:
res1, err := Method1()
if err != nil {
log.Error("method1 err, err info: %v",err)
return err
}
res2, err := Method2()
if err != nil {
log.Error("method2 err, err info: %v",err)
return err
}
res3, err := Method3()
if err != nil {
log.Error("method3 err, err info: %v",err)
return err
}
这种编码方式,使得我们的代码变得非常的臃肿,为什么需要这么写呢?因为Go的error只是一个接口,在打印error的时候并不会打印具体的错误堆栈信息。为了便于排查问题,不得已,只能通过导出打日志的形式,手动编码构建一个类似错误堆栈的日志信息,便于我们排查错误信息。
type error interface {
Error() string
}
解放我们的代码
既然这种编码方式是为了在程序发生错误的时候,能够造一个异常堆栈,那么能不能通过直接打印err就能打印出异常堆栈呢?
原生的Go语言为了保持语言的简洁性,并没有提供,我们可以借助三方库,为我们构造一个这样的堆栈,这个库就是https://github.com/pkg/errors
errors库的使用
安装errors
go get github.com/pkg/errors
errors包装原生err生成带异常堆栈的error
errors提供了很多跟Go原生错误的api相同的方法,如:errors.New
,error.Errorf
等,我们的方法在程序在发生错误的时候,如果调用了其他三方方法,我们可以通过errors.WithStack(err)
包装这个方法,得到新的err对象是会携带错误的异常堆栈。
在程序发生错误的时候,不再使用原生Go为我们提供的构建错误的方法,而是替换成https://github.com/pkg/errors
的库即可。
如这里有A方法调用B方法B方法调用C方法。那么我们只需要在方法调用的最底层方法,将错误使用errors进行包装即可。代码示例:
func A() error {
if err:=B(); err != nil {
return err
}
// do somthing
return nil
}
func B() error {
if err:= C(); err!=nil {
return err
}
// do somthing
return nil
}
func C() error {
//todo somthing err
return errors.New("C error")
}
然后我们如果想打印错误堆栈信息,只需要通过%+v
的形式即可获取
if err != nil {
log.Errorf("%+v",err)
}
此时,日志就会记录下错误的异常堆栈信息。
C error
go_test.C
/Users/Desktop/go_test/err_test.go:27
go_test.B
/Users/Desktop/go_test/err_test.go:19
go_test.A
/Users/Desktop/go_test/err_test.go:11
go_test.TestErr
/Users/Desktop/go_test/err_test.go:30
testing.tRunner
/usr/local/go/src/testing/testing.go:1439
runtime.goexit
/usr/local/go/src/runtime/asm_amd64.s:1571
使用errors需要注意的问题
- 只能包装一次堆栈信息,包装多次会打印多次重复堆栈
func A() error {
if err := B(); err != nil {
return errors.WithStack(err)
}
// do some thing
return nil
}
func B() error {
if err := C(); err != nil {
return errors.WithStack(err)
}
return nil
}
func C() error {
//todo somthing err
return errors.New("C error")
}
func TestErr(t *testing.T) {
err := A()
if err != nil {
fmt.Printf("%+v", err)
}
}
打印结果:
C error
go_test.C
/Users/Desktop/go_test/err_test.go:27
go_test.B
/Users/Desktop/go_test/err_test.go:19
go_test.A
/Users/Desktop/go_test/err_test.go:11
go_test.TestErr
/Users/Desktop/go_test/err_test.go:30
testing.tRunner
/usr/local/go/src/testing/testing.go:1439
runtime.goexit
/usr/local/go/src/runtime/asm_amd64.s:1571
go_test.B
/Users/Desktop/go_test/err_test.go:20
go_test.A
/Users/Desktop/go_test/err_test.go:11
go_test.TestErr
/Users/Desktop/go_test/err_test.go:30
testing.tRunner
/usr/local/go/src/testing/testing.go:1439
runtime.goexit
/usr/local/go/src/runtime/asm_amd64.s:1571
go_test.A
/Users/Desktop/go_test/err_test.go:12
go_test.TestErr
/Users/Desktop/go_test/err_test.go:30
testing.tRunner
/usr/local/go/src/testing/testing.go:1439
runtime.goexit
/usr/local/go/src/runtime/asm_amd64.s:1571
所以这里的建议是方法的源头进行包装,其他地方直接返回错误就可以啦~