出错处理技术[关闭]


108

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

   if err != nil {
      //handle err
   }

或这个

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

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

编辑来澄清:我不是在鼓动或建议Go团队提出更好的建议。我问的是我做对了还是错过了社区提出的一些技巧。谢谢大家


4
不,不是真的。这是一个经常讨论的话题,也是一个明智的话题。也有许多发展建议。团队的答案似乎是,编写良好的代码应该不成问题。
DenysSéguret13年


请注意,这个相关问题与这个问题并不完全相同。答案太具体了。
DenysSéguret2013年

这种烦恼也有其道理:它使得难以快速编写程序,但也使得仅通过重新抛出错误来创建错误变得更加困难。
DenysSéguret13年

您可以找到Andrew Gerrand和Brad Fitzpatrick或多或少以类似的方式在Go中编写HTTP / 2客户端的开头 youtube.com/watch?v=yG-UaBJXZ80
Supreet Sethi 2015年

Answers:


61

您的代码是惯用的,我认为这是可用的最佳实践。某些人肯定会不同意,但是我认为这是Golang中所有标准库中都看到的样式。换句话说,Go作者以这种方式编写错误处理。


12
“ Go作者以这种方式编写错误处理。” 听起来不错。
gmoore

“有些人肯定会不同意”:我不确定有人会说这不是当今的最佳实践。有人问语法糖或其他更改,但今天我认为没有任何认真的编码人员会检查错误。
DenysSéguret13年

@dystroy:好的,有人说“ it sux ”,有人说“ 错误是用返回值处理的。70的样式。” ,等等;-)
zzzz

2
@jnml以这种方式处理错误是语言设计的问题,这是一个备受争议的话题。幸运的是,有数十种语言可供选择。
2013年

4
杀死我的是如何对每个函数调用使用相同的模式。这使得代码在某些地方相当嘈杂,只是大声疾呼要使用语法糖来简化代码而又不丢失任何信息,这本质上是简洁的定义(我认为这比冗长性更好,但是可以说它是有争议的点)。原理是合理的,但是语法还有很多需要恕我直言的地方。但是,禁止抱怨,所以我现在就喝我的酷似援助;-)
Thomas

30

提出这个问题六个月后,罗伯·派克(Rob Pike)撰写了一篇题为“ 错误就是价值 ”的博客文章。

他在其中辩称,您无需按照OP提出的方式进行编程,并且提到了标准库中使用不同模式的多个位置。

当然,一个涉及错误值的常见语句是测试它是否为nil,但是使用错误值还有很多其他事情可以使用,而其中一些其他事情的应用可以使您的程序变得更好,从而消除了很多样板如果每个错误都使用了if死记硬背的语句进行检查,则会出现这种情况。

...

使用该语言可以简化错误处理。

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

这是一本好书。


谢谢!会检查出来的。
gmoore'3

这篇文章很棒,它基本上介绍了一个可能处于失败状态的对象,如果是,它将忽略您对其执行的所有操作并保持在失败状态。对我来说听起来像是单子。
水联线

@Waterlink您的陈述毫无意义。如果您稍微斜视一下,具有状态的所有内容几乎都是单子。我认为,将其与en.wikipedia.org/wiki/Null_Object_pattern进行比较会更有用。
user7610

@ user7610,感谢您的反馈。我只能同意。
Waterlink

2
派克:“但请记住:无论做什么,都要检查错误!” -80年代了。错误可能在任何地方发生,从而减轻了程序员的负担,并出于Pete的缘故采用了异常。
Slawomir

22

我同意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初始化语句

因此,就最佳实践而言,据我所知(除了在需要时使用“错误”包创建新错误之外),您已经了解了Go中几乎所有与错误有关的知识!

编辑:如果你发现你真的不能没有例外,你可以模仿他们deferpanicrecover


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代码,因此您将希望在自己的项目中使用它,或者如果您的团队同意使用它。


3
仅因为您可以执行此操作,并不意味着它是一个好主意。看起来像“ 责任链”模式,但也许更难阅读(观点)。我建议这不是“惯用的Go”。有趣的是。
史蒂文·索罗卡

2

处理golang和其他语言中的错误的“策略”是在调用堆栈中不断传播错误,直到您在调用堆栈中足够高以处理该错误为止。如果您尝试过早处理该错误,则可能最终会重复代码。如果处理得太晚,则会破坏代码中的内容。Golang使此过程非常容易,因为它使您非常清楚是在给定位置处理错误还是在传播错误。

如果您将忽略该错误,则一个简单的_将非常清楚地揭示这一事实。如果要处理它,则可以清楚地知道要处理的错误情况,因为您将在if语句中进行检查。

就像上面所说的,错误实际上只是一个正常值。这样对待它。


2

围棋诸神发布了用于围棋2中错误处理的“草稿设计”。其目的是更改错误习惯用法:

概述设计

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

反馈维基

简而言之,它看起来像:

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

更新:设计草案受到了很多批评,因此我起草了Go 2错误处理的考虑要件,并提供了最终解决方案的可能性菜单。


1

在行业中,大多数人遵循golang文档中的标准规则错误处理和Go。而且还有助于为项目生成文档。


这实质上是仅链接的答案。我建议您在答案中添加一些内容,以便如果链接无效,您的答案仍然有用。

感谢您的宝贵意见。
pschilakanti

0

下面是我减少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
    }

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

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


-1

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

再说一次,仅仅因为你能做就并不意味着你应该做


-1

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

ck IMO这样的复杂/隐藏错误处理逻辑不是一个好主意。生成的代码(需要goerr进行源预处理)比惯用的Go代码更难阅读/理解。
Dave C

-4

如果您想精确地控制错误,这可能不是解决方案,但对我而言,大多数情况下,任何错误都是一个表演障碍。

因此,我改用函数。

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)or即可log.Fatalln("some message:", err)。由于几乎没有什么比main应该做出这样的决定来结束整个程序(即从函数/方法返回错误,不要中止)的决定了,在极少数情况下,这就是您想要做的事情,这样做更干净,更好地明确地执行它(即if err := someFunc(); err != nil { log.Fatal(err) }),而不是通过不清楚其操作方式的“帮助程序”功能(名称“ Err”不好,它没有表示可能终止程序)。
戴夫C

学到新东西!谢谢@DaveC
Gon
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.