需求分析
在golang项目中,error处理是非常常见的,但golang本身提供的error
接口比较简单,仅仅是返回错误信息。我们想要的是:
- 能给每个error一个唯一标识,然后在项目中定义一些常见的、通用的error供大家使用
- 能对error进行特判,当遇到的error是某特殊error时,可以做一些特殊处理
- 使用定义好的通用error时,能重写error描述信息,从而返回符合当前具体业务场景的错误描述
最佳实践
对golang提供的error
接口进行扩展,实现符合自己要求的error,然后在项目中统一用该error。如下:
// ErrorIdentify 扩展error接口,带唯一标识。
type ErrorIdentify interface {
// 接口继承
error
// ID 返回error的唯一标识
ID() int32
}
// IError 再次扩展ErrorIdentify,扩展其他能力
type IError interface {
// ErrorIdentify 接口继承
ErrorIdentify
// Name error名称
Name() string
// Message error简单描述
Message() string
// Unwrap 返回包装的error
Unwrap() error
// Errorf 提供重写error错误信息的能力,返回一个新error(避免改变原本的error)
Errorf(format string, a ...interface{}) IError
}
// bizError 实现IError接口
type bizError struct {
ErrID int32
ErrName string
Msg string
wrapErr error
// ErrorMsgList 用于包装error的多个错误描述,有些场景用户需要重写一个error的描述,多次重写的描述通过这个字段承载
ErrorMsgList []string
}
func NewBizError(id int32, name string, msg string) *bizError {
return &bizError{
ErrID: id,
ErrName: name,
Msg: msg,
}
}
func (b *bizError) ID() int32 {
return b.ErrID
}
func (b *bizError) Name() string {
return b.ErrName
}
func (b *bizError) Message() string {
return b.Msg
}
func (b *bizError) Unwrap() error {
return b.wrapErr
}
func (b *bizError) Errorf(format string, a ...interface{}) IError {
// 之所以clone,是为了避免改变原本的error
newBizError := b.clone()
err0 := fmt.Errorf(format, a...)
if newBizError.wrapErr == nil {
newBizError.wrapErr = err0
}
// append重写的error信息
newBizError.ErrorMsgList = append(newBizError.ErrorMsgList, err0.Error())
return newBizError
}
func (b *bizError) clone() *bizError {
return &bizError{
ErrID: b.ErrID,
ErrName: b.ErrName,
Msg: b.Msg,
wrapErr: b.wrapErr,
ErrorMsgList: b.ErrorMsgList,
}
}
// Error 返回error信息
func (b *bizError) Error() string {
return fmt.Sprintf("id=%d, name=%s, msg=%s, wrapErr=[%v], errorMsgList=[%v]",
b.ID(), b.Name(), b.Message(), b.Unwrap(), strings.Join(b.ErrorMsgList, "|"))
}
// Is 比较error是否相等
func (b *bizError) Is(err error) bool {
err0, ok := err.(*bizError)
if ok {
// 通过ID比较相等
return err0.ErrID == b.ErrID
}
return false
}
测试:
// 预定义一些常用、通用的error供大家使用
var (
ErrTest1 = NewBizError(1, "TestErr1", "test1 msg")
ErrTest2 = NewBizError(2, "TestErr2", "test2 msg")
)
func TestMyError(t *testing.T) {
err1 := mockProcess1()
if errors.Is(err1, ErrTest1) {
t.Logf("mockProcess1 return ErrTest1, err1:%v", err1)
}
err2 := mockProcess2()
if errors.Is(err2, ErrTest2) {
t.Logf("mockProcess2 return ErrTest2, err2:%v", err2)
}
err3 := mockProcess3()
if errors.Is(err3, ErrTest1) {
t.Logf("mockProcess3 return ErrTest1, err3:%v", err3)
}
}
// mockProcess1 模拟业务逻辑处理
func mockProcess1() error {
// process....
// 模拟报错
return ErrTest1
}
// mockProcess2 模拟业务逻辑处理
func mockProcess2() error {
// process....
// 模拟报错
return ErrTest2
}
// mockProcess3 模拟业务逻辑处理
func mockProcess3() error {
// process....
// 模拟报错,需要自定义错误信息:返回符合当前业务场景的错误信息
return ErrTest1.Errorf("detail: %s", "error detail info")
}
运行结果:
=== RUN TestMyError
my_error_test.go:17: mockProcess1 return ErrTest1, err1:id=1, name=TestErr1, msg=test1 msg, wrapErr=[<nil>], errorMsgList=[]
my_error_test.go:22: mockProcess2 return ErrTest2, err2:id=2, name=TestErr2, msg=test2 msg, wrapErr=[<nil>], errorMsgList=[]
my_error_test.go:27: mockProcess3 return ErrTest1, err3:id=1, name=TestErr1, msg=test1 msg, wrapErr=[detail: error detail info], errorMsgList=[detail: error detail info]
--- PASS: TestMyError (0.00s)
PASS
感悟
学会面向接口编程,使用接口定义协议,然后实现协议。
学会继承已有接口,在已有接口的基础上扩展其他能力。