序
java的cause error机制是个非常好用的东西,但在原生go包中没有那么到位的支持,使用github.com/pkg/errors能够基本重现该功能,但涉及自定义错误时仍不完美,还需手动做些补充——
例子
请看如下例子,本质是利用cause err层层包裹一个错误,既有使用pkg/errors包裹的部分,也有使用自定义错误类(MyCustomError)包裹的部分。
package errors
import (
"errors"
"fmt"
"testing"
errors2 "github.com/pkg/errors"
)
type BaseError struct {
error
b1 string
}
type MyCustomError struct {
Msg string
cause error
}
func (e *MyCustomError) Error() string {
if e.cause == nil {
return e.Msg
} else {
return e.Msg + ":--cause:" + e.cause.Error()
}
}
func TestErrorsAs(t *testing.T) {
//模拟一个层叠包装的场景
err := &BaseError{errors.New("原生底层err"), "a BaseError instance"}
err2 := errors2.WithMessage(err, "使用pkg/errors包装的信息1")
err3 := &MyCustomError{cause: err2, Msg: "中层,自定义error,统一规范"}
err4 := errors2.WithStack(err3)
err5 := errors2.WithMessage(err4, "使用pkg/errors包装的信息2")
fmt.Println("//AS测试")
fmt.Println("//取中间层测试:")
var me *MyCustomError
if errors.As(err5, &me) {
fmt.Println("成功取出目标对象--", me)
} else {
fmt.Println("failure")
}
fmt.Println("//取底层测试:")
var be *BaseError
if errors.As(err5, &be) {
fmt.Println("成功取出目标对象--:", be)
} else {
fmt.Println("failure")
}
fmt.Println("")
fmt.Println("//IS测试:")
if errors2.Is(err5, err3) {
fmt.Println("IS err3")
} else {
fmt.Println("IS not err3")
}
if errors2.Is(err5, err) {
fmt.Println("IS err5")
} else {
fmt.Println("IS not err5")
}
}
附注:"github.com/pkg/errors"中的Is、As本质就是调用原生errors中的同名函数,所以这俩哪个方便就用哪个,没有区别。
运行结果如下:
=== RUN TestErrorsAs
//AS测试
//取中间层测试:
成功取出目标对象-- 中层,自定义error,统一规范:--cause:使用pkg/errors包装的信息1: 原生底层err
//取底层测试:
failure
//IS测试:
IS err3
IS not err5
可以看到,使用pkg/errors的部分能够顺利完成判断和提取,而MyCustomError那层阻碍了AS和IS的判断。
解决方法
解决方法也非常简单,令MyCustomError实现Unwrap方法。
func (e *MyCustomError) Unwrap() error { return e.cause }
运行结果如下:
=== RUN TestErrorsAs
//AS测试
//取中间层测试:
成功取出目标对象-- 中层,自定义error,统一规范:--cause:使用pkg/errors包装的信息1: 原生底层err
//取底层测试:
成功取出目标对象--: 原生底层err
//IS测试:
IS err3
IS err5
Is和As都能顺利返回预期的结果
小结
虽然是个一句话就解决的问题但其原理涉及动态语言、鸭子类型的应用,和java中通过继承来解决类似问题的思路有根本性的不同。感兴趣的可以自行搜索相关理论知识。