介绍
go-zero官网文档提供了一种错误处理的Demo,暂且称之为动态的错误处理机制。我们先看一下官方给的例子
动态处理
先看代码
package errorx
const defaultCode = 1001
type CodeError struct {
Code int `json:"code"`
Msg string `json:"msg"`
}
func NewCodeError(code int, msg string) error {
return &CodeError{Code: code, Msg: msg}
}
func NewDefaultError(msg string) error {
return NewCodeError(defaultCode, msg)
}
func (e *CodeError) Error() string {
return e.Msg
}
优劣分析
如开篇所说的,我称之为动态处理。那么何为动态处理呢?就是同一个错误码(code),支持多个错误提示信息(msg)。如一个用户不存在的code,我们返回给前端或者客户端的错误信息会根据用户名的不同,展示不同的错误信息,如fmt.Sprintf("用户%s不存在",userName)。官方提供的这种方式明显是满足动态处理的。
在我们日常开发过程中,绝大多数的错误信息,我们是可以确定的,即一个错误码(code),只对应一个错误信息(msg)。作者之前开发的项目中,通常都是用一个map来定义静态错误集合。这里我们借鉴一下用stringer来处理错误。
静态处理
先看代码
package errorx
//go:generate stringer -linecomment -type errCode
type errCode int
// 通用错误码
const (
ERR_OK errCode = 200 // 正确
)
// 用户服务涉及的错误码,前3100为表示用户服务
const (
ERR_USER_NOT_EXISTS = 100001 // 用户不存在
)
stringer自动生成code与msg的映射
// Code generated by "stringer -linecomment -type errCode"; DO NOT EDIT.
package errorx
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[ERR_OK-200]
_ = x[ERR_USER_NOT_EXISTS-100001]
}
const (
_errCode_name_0 = "正确"
_errCode_name_1 = "用户不存在"
)
func (i errCode) String() string {
switch {
case i == 200:
return _errCode_name_0
case i == 100001:
return _errCode_name_1
default:
return "errCode(" + strconv.FormatInt(int64(i), 10) + ")"
}
}
优劣分析
stringer根据type--errcode和注释信息,自动为errCode实现Stringer接口。 好处是,我们只需要定义错误码和错误信息。不足之处,比如动态处理我们可以定义一个map,快速检索到错误代码是否是我们自定义的错误类型?如果我们也增加一个map类型的数据来装载所有自定义的错误信息,这样就违背了我们的初衷--只关注定义错误信息。
动静结合处理
先看代码
package errorx
import "strings"
const defaultCode errCode = 1001
type CodeError struct {
// 重点1: 将int改成errCode类型
Code errCode `json:"code"`
Msg string `json:"msg"`
}
// 根据错误码,生成自定义的错误类型
func NewCodeError(code errCode) error {
return &CodeError{Code: code, Msg: code.String()}
}
// 动态处理错误
func NewDefaultError(msg string) error {
return &CodeError{Code: defaultCode, Msg: msg}
}
func (e *CodeError) Error() string {
return e.Msg
}
// 重点2: 不根据map来校验是否是自定义类型
// 判断错误码是否是我们自定义的错误类型
// 这里我们根据stringer,会为那些没有定义的code生成默认的msg来判断是否是我们自定义的code
func IsCodeError(code int) bool {
return !strings.HasPrefix(errCode(code).String(), "errCode(")
}
优劣分析
如果是静态的错误类型,我们只需要定义好错误码,通过stringer来自动实现Stringer接口。
如果是动态的错误类型,我们只需调用NewDefaultError,传递不用的msg,复用defaultCode。