【问题标题】:Go Error Handling Techniques [closed]Go 错误处理技术 [关闭]
【发布时间】:2013-06-06 13:23:02
【问题描述】:

我刚刚开始使用 Go。我的代码开始有很多这样的:

   if err != nil {
      //handle err
   }

或者这个

  if err := rows.Scan(&some_column); err != nil {
      //handle err
  }

在 Go 中检查和处理错误是否有一些好的习惯用法/策略/最佳实践?

编辑澄清:我不是在抱怨或建议 Go 团队想出更好的东西。我在问我是否做得对,或者我错过了社区提出的一些技术。谢谢大家。

【问题讨论】:

  • 不,真的没有。这是一个经常讨论的话题,也是一个明智的话题。也有许多进化建议。团队的回答似乎是,在编写良好的代码中应该不是问题。
  • 请注意,这个相关问题与这个问题并不完全相同。答案太具体了。
  • 这种烦恼还有一个原因:它使快速编写程序变得更难,但也更难通过简单地重新抛出错误来创建错误。
  • 您可以找到 Andrew Gerrand 和 Brad Fitzpatrick 以或多或少相似的方式在 Go 中编写 HTTP/2 客户端的开端youtube.com/watch?v=yG-UaBJXZ80

标签: go


【解决方案1】:

您的代码是惯用的,在我看来,这是可用的最佳实践。有些人肯定会不同意,但我认为这是the standard libraries in Golang 到处可见的风格。换句话说,Go 作者以这种方式编写错误处理。

【讨论】:

  • “Go 作者以这种方式编写错误处理。”听起来不错。
  • “有些人肯定会不同意”:我不确定有人会说这不是当今可用的最佳实践。有些人要求语法糖或其他更改,但今天我认为任何认真的编码人员都不会检查错误。
  • @dystroy:好的,有人说“it sux”,其他人称它为"errors are handled in return values. 70′s style.",等等;-)
  • @jnml 以这种方式处理错误是语言设计的问题,这是一个极具争议的话题。幸运的是,有几十种语言可供选择。
  • 让我很头疼的是每个函数调用都使用相同的模式。这使得代码在某些地方相当嘈杂,并且只是在尖叫着使用语法糖来简化代码而不会丢失任何信息,这本质上是简洁的定义(我认为这是比冗长更优越的属性,但这可以说是有争议的观点)。原则是合理的,但语法有很多不足之处恕我直言。然而抱怨是被禁止的,所以我现在就喝我的kool-aid ;-)
【解决方案2】:

在被问到这个问题六个月后,Rob Pike 写了一篇标题为 Errors are Values 的博文。

在那里,他认为您不需要按照 OP 提供的方式进行编程,并提到了标准库中使用不同模式的几个地方。

当然,一个涉及错误值的常见语句是测试它是否为 nil,但是对于错误值还有无数其他事情可以做,并且应用其中一些其他事情可以使您的程序更好,消除很多如果使用死记硬背的 if 语句检查每个错误,则会出现的样板文件。

...

使用该语言来简化您的错误处理。

但请记住:无论您做什么,都要检查您的错误!

这是一本好书。

【讨论】:

  • 谢谢!会检查出来。
  • 这篇文章很棒,它基本上介绍了一个可以处于失败状态的对象,如果是,它将忽略你对它所做的一切并保持失败状态。对我来说听起来几乎是单子。
  • @Waterlink 你的说法毫无意义。任何有状态的东西都几乎是单子,如果你眯着眼睛看的话。我认为将其与en.wikipedia.org/wiki/Null_Object_pattern 比较更有用。
  • @user7610,感谢您的反馈。我只能同意。
  • Pike:“但请记住:无论你做什么,都要检查你的错误!” ——这真是80年代。错误可能在任何地方发生,不再给程序员带来负担,并为 Pete 采用异常。
【解决方案3】:

我同意 jnml 的回答,即它们都是惯用代码,并添加以下内容:

你的第一个例子:

if err != nil {
      //handle err
}

在处理多个返回值时更为惯用。例如:

val, err := someFunc()
if err != nil {
      //handle err
}
//do stuff with val

您的第二个示例在仅处理 err 值时是很好的简写。这适用于函数仅返回error,或者如果您故意忽略error 以外的返回值。例如,这有时与ReaderWriter 函数一起使用,这些函数返回写入字节数的int(有时是不必要的信息)和error

if _, err := f.Read(file); err != nil {
      //handle err
}
//do stuff with f

第二种形式称为使用if initialization statement

因此,关于最佳实践,据我所知(除了在需要时使用 "errors" 包创建新错误)您已经涵盖了几乎所有您需要知道的关于 Go 中的错误的知识!

编辑:如果你发现你真的不能没有例外,你可以用defer,panic & recover模仿它们。

【讨论】:

    【解决方案4】:

    我创建了一个库,用于通过 Go 函数队列简化错误处理和管道。

    你可以在这里找到它:https://github.com/go-on/queue

    它有一个紧凑和冗长的句法变体。 以下是简短语法的示例:

    import "github.com/go-on/queue/q"
    
    func SaveUser(w http.ResponseWriter, rq *http.Request) {
        u := &User{}
        err := q.Q(                      
            ioutil.ReadAll, rq.Body,  // read json (returns json and error)
        )(
            // q.V pipes the json from the previous function call
            json.Unmarshal, q.V, u,   // unmarshal json from above  (returns error)
        )(
            u.Validate,               // validate the user (returns error)
        )(
            u.Save,                   // save the user (returns error)
        )(
            ok, w,                    // send the "ok" message (returns no error)
        ).Run()
    
        if err != nil {
           switch err {
             case *json.SyntaxError:
               ...
           }
        }
    }
    

    请注意,有一点性能开销,因为它 利用反射。

    此外,这不是惯用的 go 代码,因此您需要在自己的项目中使用它,或者如果您的团队同意 使用它。

    【讨论】:

    • 仅仅因为你可以做到这一点,并不意味着这是一个好主意。这看起来像Chain of Responsibility 模式,除了可能更难阅读(观点)。我建议这不是“惯用的 Go”。不过很有趣。
    【解决方案5】:

    在 golang 和其他语言中处理错误的“策略”是不断地将错误传播到调用堆栈,直到调用堆栈中的高度足以处理该错误。如果您过早尝试处理该错误,那么您可能最终会重复代码。如果您处理得太晚,那么您将破坏代码中的某些内容。 Golang 让这个过程变得超级简单,因为它让你非常清楚你是在给定位置处理错误还是将其传播出去。

    如果你要忽略这个错误,一个简单的 _ 会非常清楚地揭示这个事实。如果您正在处理它,那么您正在处理的错误的确切情况是清楚的,因为您将在 if 语句中检查它。

    正如上面人们所说,错误实际上只是一个正常值。这样对待它。

    【讨论】:

      【解决方案6】:

      Go 大神发布了 Go 2 中错误处理的“设计草案”,旨在改变错误习语:

      OverviewDesign

      他们希望得到用户的反馈!

      Feedback wiki

      简而言之,它看起来像:

      func f() error {
         handle err { fmt.Println(err); return err }
         check mayFail()
         check canFail()
      }
      

      更新:设计草案受到了很多批评,因此我起草了Requirements to Consider for Go 2 Error Handling 并提供了最终解决方案的可能性菜单。

      【讨论】:

        【解决方案7】:

        大多数行业都遵循 golang 文档Error handling and Go 中提到的标准规则。 它还有助于为项目生成文档。

        【讨论】:

        • 这本质上是一个仅链接的答案。我建议您在答案中添加一些内容,以便如果链接失效,您的答案仍然有用。
        • 感谢您的宝贵意见。
        【解决方案8】:

        以下是我对减少 Go 错误处理的看法,示例用于获取 HTTP URL 参数:

        (源自https://blog.golang.org/errors-are-values的设计模式)

        type HTTPAdapter struct {
            Error *common.AppError
        }
        
        func (adapter *HTTPAdapter) ReadUUID(r *http.Request, param string, possibleError int) uuid.UUID {
            requestUUID := uuid.Parse(mux.Vars(r)[param])
            if requestUUID == nil { 
                adapter.Error = common.NewAppError(fmt.Errorf("parameter %v is not valid", param),
                    possibleError, http.StatusBadRequest)
            }
            return requestUUID
        }
        

        为多个可能的参数调用它如下:

            adapter := &httphelper.HTTPAdapter{}
            viewingID := adapter.ReadUUID(r, "viewingID", common.ErrorWhenReadingViewingID)
            messageID := adapter.ReadUUID(r, "messageID", common.ErrorWhenReadingMessadeID)
            if adapter.Error != nil {
                return nil, adapter.Error
            }
        

        这不是灵丹妙药,缺点是如果您有多个错误,您只能得到最后一个错误。

        但在这种情况下,它相对重复且风险较低,因此我只能得到最后一个可能的错误。

        【讨论】:

          【解决方案9】:

          可以清理类似错误的错误处理代码(因为错误是您在此处必须小心的值)并编写一个函数,您可以使用传入的错误来调用该函数来处理错误。你不必每次都写“if err!=nil {}”。同样,这只会导致清理代码,但我认为这不是惯用的做事方式。

          同样,仅仅因为您可以并不意味着您应该

          【讨论】:

            【解决方案10】:

            goerr 允许使用函数处理错误

            package main
            
            import "github.com/goerr/goerr"
            import "fmt"
            
            func ok(err error) {
                if err != nil {
                    goerr.Return(err)
                    // returns the error from do_somethingN() to main()
                    // sequence() is terminated
                }
            }
            
            func sequence() error {
                ok(do_something1())
                ok(do_something2())
                ok(do_something3())
            
                return nil /// 1,2,3 succeeded
            }
            func do_something1() error { return nil }
            func do_something2() error { return fmt.Errorf("2") }
            func do_something3() error {
                fmt.Println("DOING 3")
                return nil
            }
            
            func main() {
                err_do_something := goerr.OR1(sequence)
            
                // handle errors
            
                fmt.Println(err_do_something)
            }
            

            【讨论】:

            • 伊克。像这样复杂/隐藏错误处理逻辑不是一个好主意IMO。生成的代码(需要 goerr 对源代码进行预处理)比惯用的 Go 代码更难阅读/推理。
            【解决方案11】:

            如果你想精确控制错误,这可能不是解决方案,但对我来说,大多数时候,任何错误都是阻碍。

            所以,我改用函数。

            func Err(err error) {
                if err!=nil {
                    fmt.Println("Oops", err)
                    os.Exit(1)
                }
            }
            
            fi, err := os.Open("mmm.txt")
            Err(err)
            

            【讨论】:

            • 这样的消息应该发送到stderr而不是stdout,所以只需使用log.Fatal(err)log.Fatalln("some message:", err)。由于在极少数情况下,除了main 之外几乎没有其他人应该做出这样的决定来结束整个程序(即从函数/方法返回错误,不要中止),这就是你想要做的事情,这样做更干净更好明确地(即if err := someFunc(); err != nil { log.Fatal(err) }),而不是通过不清楚它在做什么的“帮助器”函数(“Err”这个名字不好,它没有表明它可能会终止程序)。
            • 学到了新东西!谢谢@DaveC
            猜你喜欢
            • 2011-11-20
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2015-07-21
            • 2012-04-11
            • 1970-01-01
            相关资源
            最近更新 更多