如何确定我应该从golang API返回什么样的错误消息?

Joh*_*.19 3 rest go

我有一个带有SPA的GoLang API来使用它.我对API中的错误所做的是将它们返回到我测试的处理程序,如果存在来自先前函数的错误.如果有错误,我把它放在响应体内,将状态代码设置为400或500,然后返回响应

在处理函数中,为了能够向客户端创建清晰的消息,我需要知道返回了什么样的错误,我该怎么做?

我知道错误类型,但我读到Dave Cheney建议只返回一个错误和一条消息(换句话说换行).

但是如果API调用中可能出现这么多种错误,那么在返回响应之前它是什么意思,我需要检查它们只是为了知道我应该返回什么消息?

Zak*_*Zak 7

关于错误的第一件事就是因为存在错误接口

type error interface {
    Error() string
}
Run Code Online (Sandbox Code Playgroud)

并不意味着error从任何给定方法返回的只能附加该方法/信息.

一种常见的方法是定义自己的错误接口:

type myError interface {
    error // embeds the standard error interface
    OtherMethod() string // can define own methods here
}
Run Code Online (Sandbox Code Playgroud)

在编写方法和函数时,返回a 而不是非常重要,否则你将该方法与错误实现结合起来,并在以后引起依赖性的恶梦.errormyError

现在我们已经决定我们可以从错误中返回额外的信息,使用我们自己的错误接口你有3个主要选择.

  1. Sentinel错误
  2. 错误失败类型
  3. 行为错误

Sentinel错误

Sentinel错误是定义为包级别变量的错误值,导出并允许比较以检查错误.

package myPackage

var ErrConnectionFailed = errors.New("connection failed")

func Connect() error {
    // trimmed ...
    return ErrConnectionFailed
}
Run Code Online (Sandbox Code Playgroud)

此示例的使用者可以使用connect函数:

if err := myPackage.Connect(); err == myPackage.ErrConnectionFailed {
    // handle connection failed state
}
Run Code Online (Sandbox Code Playgroud)

您可以进行比较以检查返回的错误是否等于包的sentinel错误.缺点是创建的任何错误errors.New("connection failed")都是相同的,而不仅仅是错误myPackage.

错误故障类型

比哨兵错误略好的是错误失败类型.我们已经看到你可以定义自己的错误界面,如果我们现在说我们的错误界面:

type MyError interface {
    error
    Failure() string
} 

type Err struct {
    failure string
}

func (e *Err) Error() string {
    // implement standard error
}

func (e *Err) Failure() string {
    return e.failure
}

const ConnFailed = "connection failed"

err := &Err{failure: ConnFailed}
Run Code Online (Sandbox Code Playgroud)

在使用者代码中,您可以获得错误,检查它是否实现MyError,然后使用它执行操作.

err := myPackage.Connect()

if myErr, ok := err.(myPackage.MyError); ok {
    // here you know err is a MyError
    if myErr.Failure() == myPackage.ConnFailed {
        // handle connection failed, could also use a switch instead of if
    }
}
Run Code Online (Sandbox Code Playgroud)

现在你已经了解了导致错误的原因.但你真的关心原因是什么吗?或者你真的只关心你想要做什么来处理这个错误.

这是行为错误的好处.

行为错误

这类似于定义自己的错误类型,而是定义报告有关该错误的信息的方法.鉴于上面的示例,您是否真的关心连接失败,或者您是否真的只关心是否可以重试或者是否需要再次错误调用堆栈?

package myPackage

// this interface could report if the error
// is temporary and if you could retry it
type tempErr interface {
    Temporary() bool
}

func (e *Err) Temporary() bool {
    // return if the error is temporary or not
}
Run Code Online (Sandbox Code Playgroud)

现在在使用者中(注意您不需要使用myPackage.tempErr),如果错误是临时的并且处理重试案例,则可以使用类型断言进行测试:

err := myPackage.Connect()

if tmp, ok := err.(interface { Temporary() bool }); ok && tmp.Temporary() {
    // the error is temporary and you can retry the connection
}
Run Code Online (Sandbox Code Playgroud)

要回答这个问题,如果没有您尝试实施的服务细节,很难说.但作为广泛的建议,我会尝试尽可能多地使用3个例子中的最后一个.

如果您的服务的消费者向您发送了一些无效的输入:

err := doThing(...)

if inv, ok := err.(interface { Invalid() bool }); ok && inv.Invalid() {
    // input is invalid, return 400 bad request status code etc.
}
Run Code Online (Sandbox Code Playgroud)

如果要将特定消息返回给使用者,可以将其设为错误类型的方法.警告:这将使您的包知道它们正在Web服务中使用等.

err := doThing(...)

if msg, ok := err.(interface { ResponseMsg() string }); ok {
    // write the message to the http response
    io.WriteString(response, msg.ResponseMsg())
}
Run Code Online (Sandbox Code Playgroud)

TL; DR您需要处理所有情况,但您可以创建错误类型,使代码更容易使用!