golang 怎么优雅的实现错误码?

golang错误处理是使用return error的方式,虽然能返回错误但是没法返回错误码,比如java中的exception内有msg有error code

在golang项目如果写几个接口,接口返回值里面有error code ,但是golang 从内部函数调用中返回的error是不带code的,这就让错误码失去了意义,如果再包装一个exception结构体的话,return这个exception,但是感觉这样破坏了golang函数返回error的规则

有没有好的办法实现,没有 try catch 好麻烦

阅读 19.4k
8 个回答

go 1.14 将加如try 函数,期待ing.

新手上路,请多包涵

自定义codemessage
将之组合起来,
自定义Error实现 json 的 MarshalJSON 方法 和 Error 方法

package main

import (
    "encoding/json"
    "fmt"
)

type Error struct {
    Code    int
    Message string
}

func (e Error) String() string {
    return ErrorCodes[e.Code]
}

func (e *Error) MarshalJSON() ([]byte, error) {
    return []byte(fmt.Sprintf(`{"code": %d, "message": "%s"}`, e.Code, e.String())), nil
}

var (
    ErrorUnauthorized = 401
)

var ErrorCodes = map[int]string{
    ErrorUnauthorized: "没有进行用户认证",
}

func main() {
    e := new(Error)
    e.Code = ErrorUnauthorized
    fmt.Println(e) // 没有进行用户认证
    b, err := json.Marshal(e)
    if err != nil {
        fmt.Println(err)
    }
    fmt.Println(string(b)) // {"code":401,"message":"没有进行用户认证"}
}

标准库syscall中有一个Errno类型,就是一个系统错误码,加上自定义Error函数返回错误消息。可以参照一下。

首先定义一个错误码类型,底层用int就行了:

type ErrCode int

然后定义const错误码:

const (
    ERR_INVALID_PARAM ErrCode = 1 // 非法参数
    ERR_INVALID_COOKIE ErrCode = 2 // 无效cookie
    // ...
)

每个错误码后通过注释添加描述,然后使用go generate + stringer自动生成错误码与错误消息的对照:

//go:generate stringer -type=ErrCode --linecomment

生成文件errcode_string.go

// Code generated by "stringer -type=ErrCode --linecomment"; DO NOT EDIT.

package code

import "strconv"

const _ErrCode_name = "无效参数无效cookie"

var _ErrCode_index = [...]uint8{0, 12, 24}

func (i ErrCode) String() string {
    i -= 1
    if i < 0 || i >= ErrCode(len(_ErrCode_index)-1) {
        return "ErrCode(" + strconv.FormatInt(int64(i+1), 10) + ")"
    }
    return _ErrCode_name[_ErrCode_index[i]:_ErrCode_index[i+1]]
}

我们再为ErrCode实现error接口就ok了啊:

func (e ErrCode) Error() string {
    return e.String()
}

现在就可以把ErrCode类型传给error接口了。

go generate可以放在项目打包脚本中执行。

测试:

package main

import (
    "fmt"

    "github.com/darjun/code" // 这是我的包,替换一下
)

func main() {
    var a error = code.ERR_INVALID_PARAM
    fmt.Println(a)
    // 输出:无效参数

    var b error = code.ERR_INVALID_COOKIE
    fmt.Println(b)
    // 输出:无效cookie
}

golang 的 Error 是一个 interface ,你可以用任何复杂的类做 error ,包含你需要的所有的错误信息。

只要类里面有一个 Error() String 函数就可以了。

package main

import (
    "errors"
    "fmt"
)

const (
    UnknownErrorCode = -1
)

func New(code int, text string) error {
    return &ErrorCode{code, text}
}

type ErrorCode struct {
    code int
    s    string
}

func (e *ErrorCode) Error() string {
    return e.s
}

func (e *ErrorCode) Code() int {
    return e.code
}

func ErrorToErrorCode(err error) *ErrorCode {
    if err == nil {
        return nil
    }

    errorCode, ok := err.(*ErrorCode)
    if ok {
        return errorCode
    }

    return New(UnknownErrorCode, err.Error()).(*ErrorCode)
}

func main() {
    e1 := t1()
    e2 := t2()

    ec1 := ErrorToErrorCode(e1)
    ec2 := ErrorToErrorCode(e2)
    ec3 := ErrorToErrorCode(nil)

    fmt.Println(ec1.Code(), ec1.Error())
    fmt.Println(ec2.Code(), ec2.Error())
    fmt.Println(ec3)

}

func t1() error {
    return New(100, "test1")
}

func t2() error {
    return errors.New("test2")
}

程序输出:

100 test1
-1 test2
<nil>

error 只是一个接口,你的类型只要实现了 Error(),就可以作为 error 类型。在此基础上,你可以随意扩展。比如,定义两种类型错误,分别是 SystemError 和 UserError。

实现的时候,为它们增加成员属性 code 接口。前面其实已经有了类似的例子。比如

type SystemError struct {
    Code int
}

func (e SystemError) Error() string {
    return "系统错误,错误码 " + e.Code
}

UserError 同样可以这么处理,控制起来非常灵活简单。

ecode.go

package ecode

import (
    "fmt"
    "github.com/pkg/errors"
)

var (
    codes = map[int]struct{}{}
)

// New Error
func New(code int, msg string) Error {
    if code < 1000 {
        panic("error code must be greater than 1000")
    }
    return add(code, msg)
}

// add only inner error
func add(code int, msg string) Error {
    if _, ok := codes[code]; ok {
        panic(fmt.Sprintf("ecode: %d already exist", code))
    }
    codes[code] = struct{}{}
    return Error{
        code: code, message: msg,
    }
}

type Errors interface {
    // sometimes Error return Code in string form
    Error() string
    // Code get error code.
    Code() int
    // Message get code message.
    Message() string
    // Detail get error detail,it may be nil.
    Details() []interface{}
    // Equal for compatible.
    Equal(error) bool
    // Reload Message
    Reload(string) Error
}

type Error struct {
    code    int
    message string
}

func (e Error) Error() string {
    return e.message
}

func (e Error) Message() string {
    return e.message
}

func (e Error) Reload(message string) Error {
    e.message = message
    return e
}

func (e Error) Code() int {
    return e.code
}

func (e Error) Details() []interface{} { return nil }

func (e Error) Equal(err error) bool { return Equal(err, e) }

func String(e string) Error {
    if e == "" {
        return Ok
    }
    return Error{
        code: 500, message: e,
    }
}

func Cause(err error) Errors {
    if err == nil {
        return Ok
    }
    if ec, ok := errors.Cause(err).(Errors); ok {
        return ec
    }
    return String(err.Error())
}

// Equal
func Equal(err error, e Error) bool {
    return Cause(err).Code() == e.Code()
}

common.go

package ecode

var (
    Ok              = add(0, "ok")
    ErrRequest      = add(400, "请求参数错误")
    ErrNotFind      = add(404, "没有找到")
    ErrForbidden    = add(403, "请求被拒绝")
    ErrNoPermission = add(405, "无权限")
    ErrServer       = add(500, "服务器错误")
)

response.go

package request

import (
    "frame/pkg/ecode"
    "net/http"
)

// 响应的数据结构
type ResponseJSON struct {
    Message string      `json:"message"`
    Code    int         `json:"code"`
    Data    interface{} `json:"data,omitempty"`
}

func (a *AppRequest) Response(err error, args ...interface{}) {
    var data interface{}
    if len(args) > 0 {
        data = args[0]
    }

    ec := ecode.Cause(err)
    a.c.JSON(http.StatusOK, &ResponseJSON{
        Code:    ec.Code(),
        Message: ec.Message(),
        Data:    data,
    })
}
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
1 篇内容引用
推荐问题
宣传栏