我的golang error 最佳实践

718 阅读3分钟

背景

在做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 数据库操作异常,内部服务请转运维处理

其它优点

  1. 作为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
}
  1. 只需三行代码增加新的Error,非常方便
const ErrXXXX=Error(6) // 定义

// 加入case 返回错误提示
switch me {
 case ErrXXXX:
        return "ErrXXXX", "something wrong"
}
  1. 编译阶段检查防止Error Code重复定义
    switch不允许重复的case项,如果重复会编译错误,智能的ide将自动定位到重复的项

  2. 大于0小于0将错误进行分类
    故障单瞄一眼心里就有底,大于0业务方先查起,小于0低调点默默的找问题。降低强势扯皮最后啪啪啪打脸发生概率。

  3. 建议
    错误码从正负1000开始,999以内的保留救命用。祝愿您永远用不到999(救救救)以内。