避免检查错误是否为零重复?

时间:2013-09-12 18:16:39

标签: go

我正在学习go,我的一些代码看起来像这样:

a, err := doA()
if err != nil {
  return nil, err
}
b, err := doB(a)
if err != nil {
  return nil, err
}
c, err := doC(b)
if err != nil {
  return nil, err
}
... and so on ...

这对我来说有点不对,因为错误检查占用了大部分行。有没有更好的方法来进行错误处理?我可以通过一些重构来避免这种情况吗?

更新:感谢您的所有答案。请注意,在我的示例中,doB取决于a,doC取决于b等。因此,大多数建议的重构在这种情况下不起作用。还有其他建议吗?

6 个答案:

答案 0 :(得分:39)

这是一个常见的抱怨,有几个答案。

以下是一些常见的内容:

1 - 不是那么糟糕

这是对这些投诉的一种非常普遍的反应。你的代码中有一些额外的代码行实际上并不是那么糟糕。这只是一种便宜的打字,在阅读方面很容易处理。

2 - 这实际上是一件好事

这是基于这样一个事实,即键入和读取这些额外的行是一个非常好的提醒,事实上你的逻辑可能在那时逃脱,你必须撤消你在行中放置的任何资源管理在它之前。这通常与异常相比较,异常可以以隐式方式破坏逻辑流,迫使开发人员始终记住隐藏的错误路径。前段时间我写了一篇关于这个here的更深入的咆哮。

3 - 使用恐慌/恢复

在某些特定情况下,您可以通过使用已知类型的panic来避免某些工作,然后在您的包裹代码进入世界之前使用recover,将其转换为正确的错误,然后返回。这种技术最常见于展开递归逻辑,例如(联合国)编组。

我个人努力不要过多地滥用这一点,因为我与第1点和第2点的关系更密切。

4 - 稍微重新组织代码

在某些情况下,您可以稍微重新组织逻辑以避免重复。

作为一个简单的例子,这个:

err := doA()
if err != nil {
    return err
}
err := doB()
if err != nil {
    return err
}
return nil

也可以组织为:

err := doA()
if err != nil {
    return err
}
return doB()

5 - 使用命名结果

有些人使用命名结果从return语句中去掉err变量。不过,我建议不要这样做,因为它节省的很少,降低了代码的清晰度,并且当在纾困退货声明之前定义了一个或多个结果时,逻辑容易出现细微问题。

6 - 使用if条件

之前的语句

正如Tom Wilde在下面的评论中提醒的那样,if在条件之前的if err := doA(); err != nil { return err } 语句。所以你可以这样做:

{{1}}

这是一个很好的Go成语,经常使用。

在某些特定情况下,我宁愿避免以这种方式嵌入声明,只是为了使其自身站立起来以达到清晰的目的,但这是一个微妙而个人的事情。

答案 1 :(得分:7)

您可以使用命名返回参数来缩短一些内容

Playground link

func doStuff() (result string, err error) {
    a, err := doA()
    if err != nil {
        return
    }
    b, err := doB(a)
    if err != nil {
        return
    }
    result, err = doC(b)
    if err != nil {
        return
    }
    return
}

在使用Go编程之后,你会明白必须检查每个函数的错误会让你思考如果函数出错会导致它实际意味着什么,以及你应该如何处理它。

答案 2 :(得分:4)

如果您有许多此类重新发生的情况,其中有几个这样的情况 错误检查你可以自己定义一个如下的实用函数:

func validError(errs ...error) error {
    for i, _ := range errs {
        if errs[i] != nil {
            return errs[i]
        }
    }
    return nil
}

这使您可以选择其中一个错误,如果有错误则返回 是非零。

示例用法(full version on play):

x, err1 := doSomething(2)
y, err2 := doSomething(3)

if e := validError(err1, err2); e != nil {
    return e
}

当然,只有在功能不相互依赖的情况下才能应用此功能 但这是总结错误处理的一般前提条件。

答案 3 :(得分:2)

你看起来不对,因为你习惯于不在呼叫站点处理错误。这对于go来说是非常惯用的,但如果你不熟悉的话,看起来就像很多样板。

它确实带来了一些优势。

  1. 您必须考虑处理此错误的正确方法是在生成错误的站点。
  2. 很容易阅读代码,以查看代码中止和提前返回的每个点。
  3. 如果真的有问题,你可以通过for循环和匿名函数获得创意,但这往往变得复杂且难以阅读。

答案 4 :(得分:2)

您可以使用结果值和错误创建上下文类型。

foreach($jaren as $enkeljaar) {
   $i++;
   $options[$i] = JHTML::_('select.option',$i ,$enkeljaar->YEAR(ovl_dat));
}

答案 5 :(得分:0)

您可以将错误作为函数参数传递

func doA() (A, error) {
...
}
func doB(a A, err error)  (B, error) {
...
} 

c, err := doB(doA())

我注意到" html / template"中的一些方法。包这样做,例如

func Must(t *Template, err error) *Template {
    if err != nil {
        panic(err)
    }
    return t
}