dscrn1974
2018-06-17 16:45
浏览 114
已采纳

如何确定我应该从golang API返回哪种错误消息?

I have a GoLang API with an SPA to consume it. What I do to errors in my API is return them until the handler where I test if an error from previous functions exist. If there is an error, I put it inside the response body, set status code to either 400 or 500 then return the response

in the handler function, to be able to create a clear message to the client side, I need to know what kind of error was returned, how do I do it?

I know about error types but I read about Dave Cheney's recommendation to just return an error along with a message (or wrap them in other words).

But if there are so many kinds of errors which might occur in the API call, then does it mean before returning the response, I need to check them all just to know what message I should return?

图片转代码服务由CSDN问答提供 功能建议

我有一个带有SPA的GoLang API,可以使用它。 我对我的API中的错误的处理方式是将它们返回,直到我测试以前的函数是否存在错误的处理程序为止。 如果有错误,我将其放入响应正文中,将状态代码设置为400或500,然后在处理函数中返回响应

,以便能够创建清晰的消息 到客户端,我需要知道返回了哪种错误,该怎么办?

我了解错误类型,但我了解了Dave Cheney的建议,即只返回错误

但是,如果API调用中可能会发生多种错误,那么这意味着在返回响应之前, 我需要检查所有内容,才知道我应该返回什么消息?

  • 写回答
  • 好问题 提建议
  • 关注问题
  • 收藏
  • 邀请回答

1条回答 默认 最新

  • douying2243 2018-06-17 19:51
    已采纳

    The first thing to say about errors is that just because there's an error interface

    type error interface {
        Error() string
    }
    

    Does not mean that the error returned from any given method can only have that method / information attached to it.

    One common method is to define your own error interface:

    type myError interface {
        error // embeds the standard error interface
        OtherMethod() string // can define own methods here
    }
    

    When writing methods and functions it's really important to return an error and not myError, else you couple that method to your error implementation and cause dependency nightmares for yourself later.

    Now that we've decided we can return extra information from error, using our own error interfaces you've got 3 main choices.

    1. Sentinel errors
    2. Error Failure types
    3. Errors with Behaviour

    Sentinel errors

    Sentinel errors are error values that are defined as package level variables, are exported and allow comparison to check for errors.

    package myPackage
    
    var ErrConnectionFailed = errors.New("connection failed")
    
    func Connect() error {
        // trimmed ...
        return ErrConnectionFailed
    }
    

    A consumer of this example could use the connect function:

    if err := myPackage.Connect(); err == myPackage.ErrConnectionFailed {
        // handle connection failed state
    }
    

    You can do a comparison to check if the error returned is equal to the sentinel error of the package. The drawback is that any error created with errors.New("connection failed") will be equal, and not just the error from myPackage.

    Error failure types

    Slightly better than sentinel errors are error failure types. We've already seen that you can define your own error interface, and if we say ours is now:

    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}
    

    In the consumer code you can get an error, check if it implements MyError and then do things with it.

    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
        }
    }
    

    Now you have an idea of what caused the error which is nice. But do you really care what the cause was? Or do you only really care what you want to do to handle that error.

    This is where errors with behaviour are nice.

    Errors with behaviour

    This is similar to defining your own error type, but instead you define methods that report information about that error. Given the example above, do you really care that the connection failed, or do you really only care if you can retry or if you need to error up the call stack again?

    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
    }
    

    Now in the consumer (note you don't need to use the myPackage.tempErr), you can test using type assertions if the error is temporary and handle the retry case:

    err := myPackage.Connect()
    
    if tmp, ok := err.(interface { Temporary() bool }); ok && tmp.Temporary() {
        // the error is temporary and you can retry the connection
    }
    

    To answer the question, it's very hard to say without the specifics of the service that you are trying to implement. But as broad advice, I would try and use the last of the 3 examples as much as possible.

    If the consumer of your service sends you some input that's not valid:

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

    If you want to return a specific message to a consumer, you could make that a method of your error type. Warning: this would give your packages knowledge that they are being used in a web service, etc.

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

    TL;DR you would need to handle all the cases, but you can create error types that make the code much easier to work with!

    已采纳该答案
    评论
    解决 无用
    打赏 举报

相关推荐 更多相似问题