【问题标题】:Handle multiple errors from a single channel处理来自单个通道的多个错误
【发布时间】:2018-04-25 18:50:11
【问题描述】:

我在 go 例程和通道方面遇到了一些关于错误处理的问题。

首先我有一个监听消息的函数(在无限循环中):

func main() {

    messageChannel := make(chan messageHandler.MessageInfo)

    for {
        if token := client.Subscribe("#", 0, func(client MQTT.Client, msg MQTT.Message) {
            go messageHandler.DecodeMessage(msg, messageChannel)
            select {
            case messageInfo := <-messageChannel:
                //Handle
            }

        }); token.Wait() && token.Error() != nil {
            fmt.Println(token.Error())
        }
    }


}

但在 DecodeMessage 函数中,可能会出现多个错误。

func DecodeMessage(msg mqtt.Message, c1 chan MessageInfo) {

    //do something, might result in error

    //do another thing, might result in error

    c1 <- MessageInfo{...}
}

通常我会从函数中返回。但是对于例程来说似乎有点棘手。我查看了this post,但如果两个错误都会发生,我只会看到最后一条错误消息。

例子:

func DecodeMessage(msg mqtt.Message, c1 chan MessageInfo) {

    var returnError error

    if err != nil {
        returnError = err
    }

    if err != nil {
        returnError = err
    }

    c1 <- MessageInfo{
        Error: returnError,
        ...
    }
}

我应该有一个某种数组并附加所有错误吗?在一个例程中出现多个错误是不好的做法吗?

对我来说,最好的事情是例程会在出现错误时退出并像“正常”一样返回该错误。这可能吗?

【问题讨论】:

  • 你能澄清为什么在第一个错误之后你不能返回吗?为什么你需要在第一个错误之后继续DecodeMessage 中的下一件事?
  • 我该怎么做?我可以像往常一样使用 return 吗?
  • 是的。这种模式很常见,在其他方面看起来非常类似于你的典型 Go 模式 if err != nil { return err }
  • 但我读到了这个:stackoverflow.com/questions/20945069/…,它说这是个坏主意。
  • @gel 如果你有一个正常的函数,你会在第一个错误之后继续处理吗?如果没有,也不要在这里做。在通道上传递错误并返回。或者也许更好——在DecodeMessage 中完全避免通道并处理该函数之外的通道发送。也可能更容易测试它。

标签: go


【解决方案1】:

我首先要说的是,即使函数失败,也必须在返回之前对函数进行所有错误检查,这有点代码味道。它可能意味着您正在发生一些奇怪的事情,并且可能有更好的方法来完成您正在尝试做的事情。

但是,假设您已将问题归结为这一点,那么我会看到两个选项,具体取决于必须如何处理错误。

如果错误可以一个一个地处理并且不真正相互依赖,那么您可以创建一个错误通道并在遇到错误时将它们一一发回。请参阅以下工作示例:

package main

import (
    "errors"
    "fmt"
    "strings"
)

func main() {
    errCh := make(chan error)
    go HandleErrorsSerially("bad error", errCh)
    for err := range errCh {
        fmt.Printf("Found error serially: %v\n", err)
    }
}

func HandleErrorsSerially(msg string, errCh chan<- error) {
    if strings.Contains(msg, "error") {
        errCh <- errors.New("message contained string 'error'")
    }
    if strings.Contains(msg, "bad") {
        errCh <- errors.New("message contained string 'bad'")
    }
    close(errCh)
}

另外,如果您需要查看所有同时发生的错误(因为同时发生的两个错误可能表示某些特殊情况),那么您必须将它们全部附加到一个数组中,然后将它们传递给一个频道。请参阅以下工作示例:

package main

import (
    "errors"
    "fmt"
    "strings"
)

func main() {
    errArrCh := make(chan []error)
    go HandleErrorsTogether("bad error", errArrCh)
    errArr := <-errArrCh
    fmt.Printf("Found the following errors together: %v\n", errArr)
}

func HandleErrorsTogether(msg string, errArrCh chan<- []error) {
    errArr := make([]error, 0)
    if strings.Contains(msg, "error") {
        errArr = append(errArr, errors.New("message contained string 'error'"))
    }
    if strings.Contains(msg, "bad") {
        errArr = append(errArr, errors.New("message contained string 'bad'"))
    }
    errArrCh <- errArr
    close(errArrCh)
}

【讨论】:

    【解决方案2】:

    我可以看到返回多个错误很有用的情况,例如当您正在解析消息并且有多个错误字段并且您需要将这些总结回给客户端时。

    我认为最好的方法是使用像hashicorp's multierror 这样的包,它允许以实现错误接口的结构类型的格式收集多个错误,因此仍然可以在chan error 上发送。然后,接收方既可以将其作为标准错误进行处理,也可以提取每个单独错误的信息。

    multierror 的文档很不错,通读一下github页面上的例子就可以了。

    【讨论】:

      【解决方案3】:

      对我来说,最好的事情是例程会因错误而退出,并且 像“正常”一样返回该错误。

      当然可以,而且 get last error 或 all error 都很奇怪。

      1. 获取最后一个错误对调试没有帮助,与获取第一个错误相比。
      2. Get all error is same 如果第一个错误会导致后面的失败,下面的错误信息是无用的;另一种情况是这些错误没有有关联,我认为这意味着你必须将它们分成不同的并发部分以便更好地控制。

      好的,现在回到原来的问题。考虑代码:

      func foo(errTo chan error) {
          defer close(errTo)
          v, err := CouldFailOne()
          if err != nil {
              errTo <- err
              return // Yp, just stop this routine, let it join back to Invoker
          }
          v2, err := CloudFailTwo()
          if err != nil {
              errTo <- err
              return
          }
          // As the previous error handle until end of the function
      }
      

      如果你想从这种函数中返回值。只需使用一个通道,然后将值发送到其中,就不会引发错误。我觉得这种风格会更清晰,和返回风格一样,只是变成了使用通道返回错误。

      【讨论】:

        猜你喜欢
        • 2016-04-01
        • 2022-01-09
        • 2021-11-01
        • 2012-06-27
        • 1970-01-01
        • 2014-08-28
        • 1970-01-01
        • 1970-01-01
        • 2011-04-24
        相关资源
        最近更新 更多