背景
在做API接口、公共包等工作时,除了对外提供功能,文档也是非常重要的交付件
。文档输出的是可复制的能力
,使用者通过文档快速学习使用,不用多次找到开发人员重复答疑。
其中问的最频繁的就是错误码定义了,通常是需要列出所有返回错误的文档。
本文将分享我的go error最佳实践,该实践不仅编码非常简单
,而且写完就自动生成了交付文档
,做到了代码即文档
。
直接上代码
package api
import (
"fmt"
"runtime"
"strconv"
"strings"
)
type Error int16
const (
ErrOK = Error(0)
// 大于0的异常属于业务调用方错误,通常是需要调用方自行处理
ErrForbidden = Error(1000)
ErrParams = Error(1001)
ErrInvalidToken = Error(2001)
// 小于0的错误码为系统内部异常,通常是需要开发运维去解决的
ErrBugInvalidData = Error(-1001)
ErrDbFail = Error(-1002)
)
func (me Error) detail() (string, string) {
switch me {
case ErrOK:
return "OK", "正常"
case ErrDbFail:
return "ErrDbFail", "数据库操作异常,内部服务请转运维处理"
case ErrBugInvalidData:
return "ErrBugInvalidData", "内部产生了异常数据,属于BUG请开发处理"
case ErrForbidden:
return "ErrForbidden", "未授权"
case ErrParams:
return "ErrParams", "请求参数错误"
case ErrInvalidToken:
return "ErrInvalidToken", "鉴权Token不合法"
default:
return "Unknown", strconv.Itoa(int(me))
}
}
// ##### 以下代码无需任何变更 #####
func (me Error) Error() string {
s, _ := me.detail()
return s
}
// 转整数,返回错误码时使用
func (me Error) Code() int16 { return int16(me) }
// 生成文档
func (me Error) Doc() string {
sb := &strings.Builder{}
_, file, _, _ := runtime.Caller(0)
sb.WriteString(file)
sb.WriteString("\n")
fn := func(sb *strings.Builder, start, end, step int16) {
for {
e := Error(start)
s, d := e.detail()
if "Unknown" != s {
sb.WriteString(fmt.Sprintf("%d %s %s\n", e.Code(), s, d))
}
if start == end {
break
} else {
start += step
}
}
}
fn(sb, 0, 0, 1)
fn(sb, 1, 32767, 1)
fn(sb, -1, -32768, -1)
return sb.String()
}
一行代码生成文档
fmt.Println(Error(0).Doc())
输出:
/woo/litim/errdef.go
0 OK 正常
1000 ErrForbidden 未授权
1001 ErrParams 请求参数错误
2001 ErrInvalidToken 鉴权Token不合法
-1001 ErrBugInvalidData 内部产生了异常数据,属于BUG请开发处理
-1002 ErrDbFail 数据库操作异常,内部服务请转运维处理
其它优点
- 作为error类型,非常通用;同时做到了const定义,禁止运行时修改。外部调用后可以通过error做判断
const ErrXXX = Error(-1009)
err := api.Call()
if err == api.ErrXXX {
}
if err != nil {
http_response(w, ErrDbFail.Code(), ErrDbFail.Error(), nil)
return
}
if len(topics) <= 0 {
http_response(w, ErrForbidden.Code(), ErrForbidden.Error(), nil)
return
}
- 只需三行代码增加新的Error,非常方便
const ErrXXXX=Error(6) // 定义
// 加入case 返回错误提示
switch me {
case ErrXXXX:
return "ErrXXXX", "something wrong"
}
-
编译阶段检查防止Error Code重复定义
switch不允许重复的case项,如果重复会编译错误,智能的ide将自动定位到重复的项 -
大于0小于0将错误进行分类
故障单瞄一眼心里就有底,大于0业务方先查起,小于0低调点默默的找问题。降低强势扯皮最后啪啪啪打脸发生概率。 -
建议
错误码从正负1000开始,999以内的保留救命用。祝愿您永远用不到999(救救救)以内。