Go: 建议:单独留下“if err != nil”?

创建于 2019-06-28  ·  314评论  ·  资料来源: golang/go

Go2 提案 #32437 为语言添加了新语法,使if err != nil { return ... }样板文件不那么麻烦。

有多种替代方案:#32804 和#32811 作为原始方案并不普遍。

在混合中加入另一种选择:为什么不保持原样

我开始喜欢if err != nil构造的显式性质,因此我不明白为什么我们需要新的语法。 真的有那么糟糕吗?

FrozenDueToAge Proposal Proposal-Hold error-handling

最有用的评论

做一件事应该只有一种方式

所有314条评论

我支持这个。 我真的很喜欢如何在返回之前装饰每个错误将人类可读的文档添加到源代码中(通常我们将错误格式化为“不能[我在这些代码行中所做的]:[上一个错误]”)以及用户阅读错误。

以这种方式生成的错误信息非常丰富,而且比堆栈跟踪更容易阅读。 包含堆栈跟踪的打印错误通常假设您已准备好访问源代码(管理员可能没有此类访问权限)并且实际上了解您在代码中的方式。

没有任何形式的上下文或跟踪(裸字符串“EOF”)的错误绝对没有用。 我认为拥有更容易返回裸错误的快捷方式会让 Go 程序打印出很多无用的错误。

如果有的话,我们应该推动和支持使用上下文装饰错误,也许使用新的 vet 和 lint 规则。

我也喜欢明确的错误检查。 try令人困惑,隐式返回很奇怪。

我认为与其重新思考错误,不如尝试另一种方法来缩短这些检查的时间。

这是一个我不一定同意的例子:

value, err := foo()
return err if err != nil

这将允许更短但仍然明确的方法。 它允许添加上下文!

也就是说,内联 ifs 是 Ruby 的东西,感觉不是很 Goish,但这只是头脑风暴。 也许我们会发现别的东西。


编辑:我在这里添加了一个提案: https :

做一件事应该只有一种方式

[...]为什么不保持原样?

我认为可以公平地说,我们都知道这个问题的答案。 如果您真的不知道,您只需阅读各种建议中的一项即可找到答案。

IMO,这里的细节太少,我们无法进行重点讨论(即我认为它不符合提案的条件),它很快就会变成另一个自行车棚,里面充满了使代码可读性降低的圈子抽搐和想法.

这么多这个。

可以说我进入 Go 是因为这种显式的错误处理。 它介于许多语言使用的隐式 try-catch 和 Option 或 Maybe 之类的函数类型之间,后者有利于返回给用户并被显式处理。

我不确定一个新的结构是否真的能解决这个问题。 如果您将if err := nil包裹在这样的辅助函数中,它可能会有所帮助(请原谅我生疏的 Go):

func handleErr(err error, cb func(error)) {
        if err := nil {
                cb(err)
        }
}

但是使这个辅助函数不太有用的问题是类型系统,这是一个不同的主题。

我支持这个。 if err != nil { return err }不是我们代码库中任何代码的一部分。 因此,尝试“宏”根本没有任何意义。 我们只返回包装的错误,以及描述上下文的消息。

通过 defer 添加上下文也没有意义,因为我们想要返回不同的错误消息来区分不同类型的错误。 不过, try(fn(), "my error message: %w")可能有用。 但即便如此, if err != nil构造可能仍然更可取,因为行长度较短。

坦率地说,我不想要try提供的隐式回报。 如果我们有泛型,我更喜欢使用 monad-ish 行为的解决方案。

type Result<T> interface {
  Expect(err error) T
  OrElse(defaultValue T) T
}

func From<T>(value T, err error) Result<T> { ... }

对我来说,这比目前提议的内置函数要干净得多,尽管需要对上述内容进行进一步的更改,因为您将有大量返回 (value, error) 和 Result 的方法

当前的try提案无法明确修饰错误,不能满足我的需求。 我无法想象曾经使用过它。 坦率地说,它也可以称为code_smell

改变它可能没有意义,因为错误的问题正在试图解决。

我们熟悉的代码不是错误处理。

if err != nil {
  return err
}

这是错误nil处理。 在此模式中,任何时候都没有处理错误的值。

如果我用另一种语言来证明这一点,Ruby。

begin
 some_method_that_raises_an_error
rescue => e # catch any exception
  retry e        # throw it up again
end

这传递了与 golang 代码相同的行为。 当我们检测到发生了异常然后重新提出它时。 我们只是把它扔到堆栈上。

在 golang 中,我们return它。

实际_错误处理_发生在哪里?

对于这种模式的失败,我们都有过类似的经历。 例如,收到file not found错误,然后花费大量时间跟踪此错误的原始 _thrower_。

这就是为什么我认为try提案(和其他提案)是错误的。 我们没有一个很好的模式来实际处理错误。

我已经看到err.Error()字符串检查、类型断言等来实际检查错误。
对于这种不一致,我们需要一个模式。 感觉xerrs可能正在解决这个问题,但它还没有完成。

我支持保持 err!=nil 原样检查。

每次我深入研究相当大的 Go 代码库时,我都会问自己如何减少一些样板文件。 我总是回到:

  • 这些代码路径以一种或另一种方式存在。
  • 即使您不需要隐藏代码路径,让人们有机会隐藏它也会使默认行为成为默认行为(因为显然我们仍然通过行数来衡量语言的使用难度)。
  • 如果默认行为隐藏了代码路径,那么我会寻找新的“丢失清理”错误。
  • 返回错误的含义和模式多种多样,因此该提案只能捕获感知问题的一部分
  • 如果只捕获了一部分,那么我们肯定会得到一堆解决方案
  • 有了一堆解决方案,就会有一些用例自适应魔法来卷起它们的诱惑
  • 如果这确实是一个问题,那么人们可以自由地创建自己的简单解决方案或使用一些广泛采用的模式。 我没见过那样的东西。 也许我只是看起来不够努力。

问题跟踪器对很多事情都有用,但它没有用的一件事是对复杂主题的详细讨论。 问题跟踪器不提供线程,回复特定消息很尴尬。 由于这里没有实际的提案,只是对其他提案的回应,我真的强烈建议您将此讨论带到 golang-nuts 邮件列表。

如果可以的话,我相信这就是答案。 这个新的错误提议与语言的目标直接冲突。

我喜欢 golang 的原因是因为它的简单性和控制流的清晰使用。 关于 Java,我最鄙视的一件事是 try throw 构造。 太恶心了它鼓励可怕的错误处理。 将异常向上发送到调用堆栈是一种处理控制流的可怕且令人作呕的方法。 最重要的是,它鼓励将所有内容包装在一个巨大的检查中并称其为一天,而不是对每个错误情况进行自我记录和显式处理。

If err != nil 鼓励良好的错误处理,自我记录并鼓励关于特定案例的良好记录,老实说,这是我最喜欢 go 的事情之一。 使这个新的控制流中断,使用凌乱的、有些模棱两可的返回和参数以及令人困惑的语义,这符合我所喜欢的语言的精神。

冗长并不是一件坏事。 不必要的冗长是,但我认为 go 的错误处理不是不必要的。 这是语言魅力的一部分。

不能同意更多。 显式错误处理是 IMO 语言的最佳特性之一。 我总觉得很多被它困扰的人只是还不习惯它。

将问题分开是不好的,但我认为在这种情况下,两种意见合并为一种意见。

  1. 我们不喜欢新语法(尝试或新的 if-err 语法)
  2. 无论如何,我们不想添加新的语法

GitHub 投票图标无法解释第二个。

go 中的显式错误处理是我喜欢 golang 的原因之一。 我不明白为什么任何 Go 开发人员都希望以其他方式使用它。 我认为添加新语法的提议主要来自那些习惯于使用其他语言使用的语法的人。 可能需要一些时间来适应,但是一旦你习惯了它就可以完美地工作。

我写了#32811,我更支持这个提议……我宁愿不理会错误处理。 我认为对这个提议的表情符号反应说了很多。

我个人同意让错误处理保持原样。 我喜欢 Go 的一件事是它的语言是最小的,并且一般来说有一种做事的方式。 通过添加用于错误处理的新语法,我们将创建一个世界,其中 x% 的代码使用当前方法,而 y% 使用新方法。 在已经讨论过的其他问题中,这将创建不一致的代码库。 我个人认为新的错误处理语法的价值不值得权衡,因为我认为现有的语法足够/足够。

作为刚接触 Golang 的人,我觉得这门语言让我耳目一新的一件事显式错误处理。 我在 Java、Ruby、Python 和 Node 方面工作过很多,处理错误比在 Go 中要繁重得多。 我宁愿看到错误的清晰“路径”,也不愿通过某种语言结构向我暗示,使其更加模糊。

ˋreturn ... if ...ˋ 来自@andreynering的建议实际上相当聪明恕我直言。 保持代码明确(没有隐藏的控制流中断),同时减少样板(单行)。

同意,别管if err != nil

我更喜欢当前的格式。 教学模式清晰易懂。 让新工程师跟上速度很简单,因为他们可以学习一种简单的模式并重复它。 它还要求用户至少考虑当前上下文中的错误,确保至少工程师承认这里可能发生错误,我需要考虑该怎么做。

我写了#32804,我宁愿看到事情没有改变。 如果你的代码很长,那是因为它做了很多事情。 如果您有很多错误处理代码,那是因为您在处理所有案例方面做得很好。

请不要为了添加东西而添加东西。

我喜欢错误处理的简单性。

Expect 只是一个 anagram for except,我宁愿不使用它。 谢谢你开始这个。

请不要改变我的圣杯。

有压倒性的社区反馈要求更简化的错误处理(来自年度调查)。 Go 团队现在正在解决这个问题。

@icholy当然可以,但目前的提案还有很多不足之处。 它们似乎都混淆了错误处理,恢复到更多的 try/catch/finally 样式实现,将错误处理从上下文中冒出来,或者以其他方式使其更加复杂。 由于 Go 应该是一种简单的语言,我想我们很多人都希望有一个简单的选择。 我没有看到任何我个人喜欢的,所以我认为更好的选择是保持当前的模式。

一个抱怨是必须输入它,但几乎每个编辑器都有插入代码片段的快捷方式,所以这真的没什么大不了的。 也许这是我从 1.0 之前开始使用 Go 的经验,但我碰巧喜欢它的简单性,不介意冗余。

@kevineaton你认为try很复杂吗?

我完全同意这一点。 我个人甚至不相信我们需要做任何事情 - 我同意if err != nil支票乍一看看起来很尴尬,但我还没有看到任何建议实际上解决问题而不广泛违反正在发生的事情流行为。

@icholy在 Go 之前花了十年时间编写 Java 和 Python 之后,我认为可以。 我认为您会遇到 Pokemon 异常捕获或多个异常的捕获链,否则会引入更多的开销和样板。 如果我能避免这种错误处理方式,我就不会再回到这种错误处理方式了,因为它几乎总是在教学时导致头痛和困惑。 我还在作为软件架构师的日常工作中教授计算机科学,因此我偏向于教授新开发人员和指导。 我会选择 Go,它是简单的错误处理,而不是任何一天可能更复杂或更细微的错误处理。

问题跟踪器对很多事情都有用,但它没有用的一件事是对复杂主题的详细讨论。

是不是_那_ 真相。 但我们来了。

如果添加try if err != nil不会消失。 我相信try将为代码路径增加一些清晰度,这些代码路径要么是错误转发,要么在一个延迟错误处理程序中可以轻松总结出许多不同的错误。 . 我真的不明白try鼓励不处理比一堆空的if-err-return-err多得多的错误。 无论try是否存在,都很容易忽略实际处理错误。 我认为try是错误处理的最佳建议之一,因为它看起来很容易阅读使用它的代码。

我不请自来的两分钱,只是感觉不太“走”。 这太神奇了,我们将采用隐式构造而不是显式构造。

来自 Go常见问题解答

为什么 Go 没有 ?: 运算符?
_Go中没有三元测试操作。 您可以使用以下方法来获得相同的结果:_

if expr {
   n = trueVal
} else {
    n = falseVal
}

Go 中没有 ?: 的原因是该语言的设计者已经看到该操作过于频繁地用于创建难以理解的复杂表达式。 if-else 形式虽然更长,但无疑更清晰。 一种语言需要

@ianlancetaylor

问题跟踪器对很多事情都有用,但它没有用的一件事是对复杂主题的详细讨论。 问题跟踪器不提供线程,回复特定消息很尴尬。 由于这里没有实际的提案,只是对其他提案的回应,我真的强烈建议您将此讨论带到 golang-nuts 邮件列表。

您可以回复特定消息。 我刚刚回复了你的。 :)

由于这里没有实际的提案,只是对其他提案的回应,

对我而言,提案意味着呼吁变革。 这个特殊的问题是反变化。 您是否建议我们创建一个_不_更改错误处理的提案? 我认为提案系统很棒,但它使现状代表不足。

在花了十年时间编写 Java 和 Python 之后……我还在作为软件架构师的日常工作中教授计算机科学

@kevineaton你吸

这个问题在一个半官方的地方作为一个长期持续的民意调查,基本上任何人都可以很容易地投票赞成或反对提案。

不更改语言以删除if err != nil是一个完美的提案,基本上不需要添加任何细节。 我不确定问题是什么。 不,这不是上帝可怕的漫长而难以理解的。 这并不意味着它是错误的、糟糕的或不足的。

+1,如果没有更好的东西,那么好的东西将是一个非常好的堆栈跟踪信息(没有框架舞蹈的东西),我想x/errors已经实现了这一点,但是,我会喜欢在不久的将来快速的东西,比如标记func使用throws关键字将返回error + try关键字,防止错误变量阴影(我个人讨厌),如下所示:

func a() (int) throws {
  throw &someError{}
}

anInt, err := try a()

@icholy这是非常不需要的。 这是一个讨论的地方,Go 社区应该是一个受欢迎的社区。 没有地方发表这种评论。 我相信苏格拉底对辩论中的侮辱有话要说。

当前的错误处理很容易出现人为错误。 现在很容易忘记检查err 。 如果范围内已经有任何检查(并且大部分时间都有),编译器将不会以unused variable终止。 错误处理应该是严格的——你要么_一个错误,要么检查它 - 不应该进行腿部射击。

@kevineaton你认为try很复杂吗?

try是一种代码气味。 它强制在整个代码块中缩进,而不是只在一个地方缩进。 此外,异常处理的“冒泡”性质在代码和多个退出点中创建了事实上的不确定性行为。

使用多个返回值而不是try的美妙之处在于,只有一个值可以检查您的函数何时完成,以及一个退出函数的点(当然,除非使用保护语句或其他显式返回)。

try块破坏了多重回报的整个目的。

@fillest虽然它会降低代码的可读性,但我确实认为这在安全/显式错误处理方面会增加价值。 如果你回顾一下我们在 Go 中如何处理错误的最初目标,我认为这将是一个很好的迭代,可以避免你引用的错误类别,同时仍然追求明确的善良精神。

当前的错误处理很容易出现人为错误。 现在很容易忘记检查错误。 如果范围内已经有任何检查(并且大部分时间都有),编译器将不会以未使用的变量终止。 错误处理应该是严格的——你要么 _ 一个错误,要么检查它 - 不应该是腿部射击。

@fillest对错误处理的拟议更改使“腿部射击”更容易并且错误更加明显,因为它们可以被懒惰地处理。

我停止使用 Go 是因为缺乏泛型、样板倾向、GC、缺乏资源限制/会计以及不了解编译器功能的 PHP 菜鸟产生的工作量。 Haskell、C# 和其他人很好地解决了错误处理......如果 Go 2 提案像以前一样有明确的案例处理(不确定),它看起来没问题。

错误处理是编程的核心。 对业务逻辑进行建模(无论它有多复杂)总是比响应此逻辑生成的无效条件更简单。 简单地转发错误是一种代码异味。 我希望 Go 不鼓励这种行为,而是促进错误管理模式。 初学者经常对所有这些错误处理代码感到困惑,因为他们还没有意识到错误管理的中心化程度。

完全同意,因为内置的try将无助于包装错误并向它们添加信息,即使是一点。

在用try重写之前:

_, err := doSomething()
if err != nil {
    return nil, errors.Wrap(err, "failed to do something")
}

_, err = doOtherThing()
if err != nil {
  return nil, errors.Wrap("failed to do the other thing")
}

想象一下用try重写后会发生什么。

由于try已经通过将其参数括在括号中来充当 1 参数函数,因此它可以接受作为错误包装代码的第二个参数。

try(extract_value(try(get_data(1), errors.Wrap(err, "failed to get data")), errors.Wrap(err, "failed to get data")))

err值必须隐式引入(以一种卫生的方式)。 然后,如果try用作 1-argument 函数,那么它只会返回其错误不变。

我同意,唯一可能使错误处理更简单的“语法糖”是当我们的函数有多个返回时让我们做以下类似的事情......下划线只是返回类型的默认值

if err != nil {
    return _, _, err
}

@sorenvonsarvort对我来说似乎并没有那么糟糕:

var errContext string 

defer func() {
  // err is a named return
  if err != nil {
    err = fmt.Errorf("%v: %w", errContext, err)
  }
}()

errContext = "failed to do something"
_ := try(doSomething())

errContext = "failed to do other thing"
_ := try(doOtherThing())

根据我的理解,如果该特定代码段更清晰,您仍然可以使用if err != nil { ... }

try在其他情况下大放异彩。 想象一下更像:

func trySomeComplexOp() (r result, err error) {
  a := try(step1())
  b := try(step2(a))
  c, d := try(step3(b))
  return try(lastStep(c, d)), nil
}

与上面的代码相比,像上面这样的代码可以比你必须在if err != nil块中撒上干净得多。 Go 是关于“线性可读性”的,所以我认为try在这方面做得很好。

有压倒性的社区反馈要求更简化的错误处理(来自年度调查)。 Go 团队现在正在解决这个问题。

这是一小部分人,我敢打赌他们中的很大一部分甚至不使用围棋

@sirkon你的声明基于什么?

@sorenvonsarvort对我来说似乎并没有那么糟糕:

与上面的代码相比,像上面这样的代码可以比你必须在if err != nil块中撒上干净得多。 Go 是关于“线性可读性”的,所以我认为try在这方面做得很好。

在俄罗斯,我们称之为“экономия на спичках”。 使用谷歌翻译得到一个意思。

对于那些在此线程有没有准备好,我会建议你阅读这条评论对原try建议的问题。 它讨论了一般错误上下文最佳实践以及如何用try

我认为在 Go 社区中,错误上下文可能已经变得有点教条化了。 我知道我个人已经为此而堕落,并过度将我的错误置于上下文中,导致消息非常冗长、重复且难以阅读。 关于何时将错误上下文化以及何时不上下文存在很多细微差别。

我喜欢try基本上是一个快捷方式,并减少了一些样板代码。 但是我们失去了用额外信息包装错误的能力。 但是,以下更改可能会解决此问题:

f := try(os.Open(filename))

变成

f := try(os.Open(filename), "open data file")

当然,如果您需要做的远不止这些,仍然可以使用“完整”方式进行err != nil检查。

我同意这一点,但我将尊重围棋团队的要求,在做出最终意见之前,要对变更有更多的经验。

但是我对更改的初步经验似乎支持它确实没有必要。 我有 2 个“真实世界”程序,每个程序大约有 10k 行,并且在这两个程序上运行tryhard表明它们都不会从这种更改中受益。 这很容易解释,因为两者总是为错误添加上下文。 我在 Go 中有其他较小的“玩具”程序, tryhard确实找到了一种情况,我可以在其中一个中使用try ,但仅此而已。

我承认其他人对待错误的方式可能与我不同,我承认try可以以积极的方式使用。 tryhard源代码本身有一些连续的return err ,如果它使用try我认为可读性不会受到太大影响。 但我只是担心滥用,因为这些会影响易读性。 这里提供

另外,我喜欢人们通常如何阅读 Go 代码,即使他们自己不会编程。 这将使他们必须学习try的神奇之处,特别是因为它与他们在其他语言中看到的其他try作用不同。 对于刚接触这门语言的新人来说也是如此,这只是他们必须学习的另一项功能,这种语言以“只有您需要的功能”而以简单为荣。

让我们等着看。 我将针对此更改进行更多试验,但我不认为它会改变我的立场。

有压倒性的社区反馈要求更简化的错误处理(来自年度调查)。 Go 团队现在正在解决这个问题。

@icholy就像我说的,我确实喜欢为错误添加上下文。 在这方面,“更简化的错误处理”对我来说意味着提供该上下文并从中获取信息的更好方法。 例如,对于我添加到错误中的所有上下文,如果错误是由超时引起的,那么询问“上下文”应该是微不足道的。 但事实并非如此。 您通常不得不使用pkg/error ,制作自己的“错误结构”和/或制作方法来使用它们,根据实现可以诉诸于进行字符串搜索。 我宁愿看到一些可以让我免于制作整个结构和方法的东西,而不是那些偶尔为我节省一个if 。 就像之前所说的那样,当此更改基本上提供了一种更方便的方法来不真正处理错误时,您真的可以称其为“错误处理”吗?

你认为try很复杂吗?

孤立地try并不复杂,但不会孤立地考虑语言变化。 考虑:

  • 学习新内置的语义增加了认知负担
  • 由于使用try而降低可读性,因为它更短,在应该使用if err != nil {return ... errors.Wrap() }情况下

我赞同上述观点,即简单性(有一种检查错误的方法)比检查错误的简短方法更重要。

处理错误的方法不是通过全局语句,如果这是要忽略的事情,那么如何处理恐慌情况? 我会支持更好的错误处理,因为今天其他编程范式处理错误但不要忽略

我认为try提案没有任何问题?
如果您想使用旧行为或需要其他方式处理错误,为什么不重新使用旧方式? 没有人用刀抵着你的脖子压迫你使用try语法?
try语法很简单,而且是减少样板代码的好方法,我不知道为什么 gopher 喜欢虐待狂的方式?

如果 gofmt 被更改为一个语句 if 块保留在一行上怎么办?

这样,我们可以让包装错误只需要一行来处理,而不是大多数情况下的三行。 这将减少错误处理占用的垂直空间,并使错误处理看起来似乎没有占用平均函数中超过一半的垂直空间。

这是它的样子:

// we already have an err in scope
err = frub.Confozzle(foo, bar, baz)
if err != nil { return errors.Wrap(err, "confozzling didn't work") }

我认为在 Go 社区中,错误上下文可能已经变得有点教条化了。 我知道我个人已经为此而堕落,并过度将我的错误置于上下文中,导致消息非常冗长、重复且难以阅读。 关于何时将错误上下文化以及何时不上下文存在很多细微差别。

错误包装和堆栈帧/错误打印的东西会更直接。 我认为及时会有一个很好的解决方案,但不是现在。我个人更愿意等待引入更强大的 go2 功能,直到解决参数多态性,因为它在设计其余功能时可能很有用

我完全同意保持原样。 它有点过于冗长,但很容易理解。

如果我能减少

if err := foo.x(a, b); err != nil {
    return err
}

if err := foo.y(); err != nil {
    return err
}

if err := foo.z(c); err != nil {
    return err
}

if err := check foo.x(a, b), foo.y(), foo.z(c); err != nil {
    return err
}

恕我直言,如果不改变习语太多,也许也会很棒。

@henvic我认为问题在于您假设您想以相同的方式处理这三个错误。 Go 迫使您考虑如何单独处理每个错误。 进行单独的错误检查可以清楚地说明这一点。 将它们聚合为一个会使开发人员在认知上返回并检查,这些错误_真的_应该以相同的方式处理吗? 我认为在这个提案中,我们失去了清晰度,并被迫考虑了一些少击键的错误。

@sanbornm ,你是对的。 我同意。

这个check也可能是一个checkNonZero运算符,它只接受一个返回参数并在第一个非零值(如 null)处返回。 但除了过于含糊,更影响语言。 它甚至只会稍微暗示你不应该使用它,例如,io.EOF。 另一种可能性是,也许,依靠go vet至少让您知道最常见的情况(例如从管道读取时的 io.EOF)...但这个想法听起来并不擅长都给我。

我认为try提案没有任何问题?
如果您想使用旧行为或需要其他方式处理错误,为什么不重新使用旧方式? 没有人用刀抵着你的脖子压迫你使用try语法?
try语法很简单,而且是减少样板代码的好方法,我不知道为什么 gopher 喜欢虐待狂的方式?

我们都生活在社区中。 每天我都会处理很多其他人编写的代码。 因此,即使我不使用语言,语言的变化也会影响我。

在大型项目中,最糟糕的日志是由我们忘记某人编写的函数生成的,该函数调用一个库,该库调用一个库,该库调用一个库,该库抛出一个通用的new Exception()直到“Pokemon”异常处理程序没有捕获到并记录了一个通用错误。 第二个最差的是相同的,但有一个难以理解的数百行堆栈跟踪,我想我们最终可以找出原因(幸运的是,只需搜索github.com/<us>/<ourproject>找到大部分相关信息,但是有时有很多)。 尽管名字叫“异常”,但在大型 Java 项目中,“异常”却是非常普通的。

同时,即使有很多冗余上下文,像"narf: Error unpoiting the zort: foo: Unexpected bar in baz: {\"ork\": \"morpork\"}"这样的简单 Go 错误字符串(以我的经验)通常很容易解释,只要我们一直努力将重要的上下文嵌入到实际误差值。 如果事实证明缺少重要的上下文,那_也_通常是相当明显的。 在这些情况下,“修复”是添加更多上下文并等待另一个错误,因此它并不完美,但总的来说,我仍然更喜欢滚动堆栈跟踪和/或依赖我的依​​赖项的依赖项的依赖项来“抛出” " 或 "raise" 理智的错误消息。 我真的很欣赏panic()这个名字似乎阻止了大多数 Go 开发人员如此自由地部署本质上相同的语言功能。 毕竟,我们不要让errorpanic成为同一件事。

我偶尔会遇到一个函数有十几种失败模式的情况,而其中绝大多数都应该得到相同的错误消息。 重复并没有真正困扰我,但通常我团队中的其他人会困扰我,所以我们通过在函数开始时声明一个闭包来处理这些常见错误来妥协。

func foo(a, b, c SomeArgType) (x, y, z SomeReturnType, err error) {
  handleError := func(handleErr error) (x, y, z SomeReturnType, err error) {
    log.WithFields(logrus.Fields{
      "package": "foo",
      "func": "foo",
      "arguments": map[string]SomeArgType{"a": a, "b": b, "c": c},
      "error": handleErr,
    }).Error("Error fooing the bar")
    return reasonable, default, values, handleErr
  }

  err := doABunchOfThings()
  if err != nil {
    return handleError(err)
  }
}

诚然,这在某些方面_仍然_是一个不完美的解决方案。 但我喜欢这样做让未来的开发人员仍然可以很容易地理解foo何时返回以及返回什么,而无需过多地跳转控制流。

如果以某种方式这种重复“问题”在众多包中非常普遍,而不是(正如我通常看到的那样)仅限于不可约复杂包中的少数不可约复杂函数,那么项目范围的“函子”可能会被用于类似的结局,并且(叹气)如果参数类型的概念最终被添加到语言中,您可以在错误处理工厂工厂后面隐藏更多细节,而无需依赖于 try/catch。

@托马斯夫

错误包装和堆栈帧/错误打印的东西会更直接

我同意。

就我个人而言,我更愿意等待引入更强大的 go2 功能,直到解决参数多态性,因为它在设计其余功能时可能很有用

最初的try提案中的一些人也讨论了等待泛型,但我不清楚参数多态性如何使try提案有任何不同。 它已经是内置的,因此它不仅限于我们可以用语言表达的内容的限制。

@icholy

有压倒性的社区反馈要求更简化的错误处理(来自年度调查)。 Go 团队现在正在解决这个问题。

只是为了回应这个; 英国也有多数人支持脱欧。 当然,欧盟也带来了一些公众应对的不利因素。 然而,一旦提出了所有替代方案,留在欧盟似乎终究不会那么糟糕。

现在我完全不打算将此政治化,您可能不同意上述观点。 但我想表明的是,即使大多数人最初认为某事是令人讨厌的,但一旦检查了所有替代方案,它仍然可能是最佳解决方案。

我对错误处理没有强烈的看法,但它_可能_成为保持现状的论据。

在专业的编码环境中,我们利用当前的错误处理实践来注释跟踪系统和装饰日志。 顺便说一句,隐式返回就像在导出的库函数中使用恐慌一样,因为它掩盖了直接的流控制可读性。

@icholy

有压倒性的社区反馈要求更简化的错误处理(来自年度调查)。 Go 团队现在正在解决这个问题。

只是为了回应这个; 英国也有多数人支持脱欧。 当然,欧盟也带来了一些公众应对的不利因素。 然而,一旦提出了所有替代方案,留在欧盟似乎终究不会那么糟糕。

你不需要认真考虑这个人的说法:表情符号的数量表明人们通常不喜欢try提议,他们通常喜欢这种“保持原样”的提议。

PS 在我的实践中,绝大多数在其主要领域(网络服务、CLI 实用程序)不喜欢 Go 的人甚至没有使用过它。 所以我宁愿忽略他们的意见。

我们需要比try提案更好、争议更少的选项。
我没有看到草率解决方案的紧迫性。

@velovix我认为我更讨厌参数多态性而不是 try/catch 错误“处理”,但如果它确实成为一种语言功能,我可以看到它可以通过几种方式避开对另一个内置语言功能的需求。

一方面,当人们不想重复的代码是样板时:

foo, err := Foo()
if err != nil {
  log(err)
}
bar, err := Bar(foo)
if err != nil {
  log(err)
}
// ...

然后参数函数、类型推断和_maybe_ 或_optional_ 样式对象设计模式的一些组合将直接(heh)减少样板,而无需求助于奇怪的非线性控制流策略:

func<T> DoWithErrorLogging(f func(any...) (T, error), args... any) T {
  t, err := f(args...)
  if err != nil {
    log(err)
  }
  return t
}
// ...
foo := DoWithErrorLogging(Foo)
bar := DoWithErrorLogging(Bar, foo)

IMO 这一切都会比 Go1 差很多。 但比在语言中使用这个 _plus_ try/catch 关键字更好。

老实说......就目前的情况而言,我认为我最喜欢的 Go2 的“破坏性”更改只会解决 Go1 中的所有小不便,例如net/http默认值是嵌套在可变共享全局变量中的可变共享全局变量(只是使 Hashicorp 的cleanhttp标准,基本上),或者(*time.Timer).Reset()具有您只需要知道整个 syscall 包。 Go3 几乎可以立即发布,无论人们想在上面生长什么肿瘤; 我不明白为什么需要在一个版本中完成小的和大的重大更改。

我赞成try ... 谨慎使用。 我怀疑流行的项目会添加关于何时/是否可以使用 try 的指南,并且小型/新/单人项目有时会因try而遭受严重错误 - 从而缺乏使用

我真的不认为在语言中添加try会那么可怕。 如果人们对最坏情况的恐惧被证明是有根据的,那么它的使用将是不受欢迎的。 没有经验的人会不加选择地使用它,而其他人则不会。 这不是世界末日。

如果添加了try ,我可能会在某些情况下使用它。 这些情况是,返回错误,但我认为实际上发生错误的可能性非常小,以至于我看不到添加上下文的意义,我只是按原样返回错误。 例如,如果我刚刚创建了一个文件系统来填充我知道是 1 TB 的磁盘,我可以确定有空间来创建 1kb 的文件或目录。 如果失败,我不想忽略错误 - 它可能表明其他地方存在错误、硬件故障等。但是,真正值得花精力注释每个非常不可能的错误。

我不喜欢 try(..) 只是在编码人员的思维堆栈上再放一对括号/方括号,以便在打字时考虑。 我能想象的最长时间!

所以这更好:
值,错误:= foo()
如果 err != nil,则返回 err

但这仍然很常见。 所以我想如果像这样的 smt 可能以某种方式成为可能:

值,检查错误 := foo()

如果 Go 想要成为一种可读的语言,那么应该尽量减少难以阅读或理解的能力。

如果 Go 想要良好的错误处理,它应该鼓励错误在调用堆栈上升时具有额外的上下文。 使用 defer 处理错误的要求似乎令人困惑。 如果您的错误处理程序有错误怎么办? defers是按栈顺序执行的,我们需要反向声明handler吗?

If 语句是直截了当的,在这里几乎没有歧义的余地。 我觉得try正在解决一个 nit 而不是真正的工程问题。 我喜欢这个提议,因为它允许沉默的大多数在没有完全理解这些复杂提议的方方面面的情况下最终发表声明。

@icholy请保持礼貌。 请注意 Gopher 行为准则: https :

大家:除了我上面的评论(https://github.com/golang/go/issues/32825#issuecomment-506740412),请注意https://golang.org/wiki/NoPlusOne。 发表评论只说“我同意”或“我不同意”是没有帮助的。 请改用表情符号按钮。 谢谢。

@sanbornm

(我同意可以回复消息;我说这很尴尬,并非不可能。我关于线程的观点仍然存在,因为这个迷你线程在其他评论的暴风雪中消失了。)

对我而言,提案意味着呼吁变革。 这个特殊的问题是反变化。 您是否建议我们创建一个不更改错误处理的提案? 我认为提案系统很棒,但它使现状代表不足。

没有必要创建提案 A 说不应该采用提案 B。 相反,投票否决提案 B。 有关提案 B 的详细讨论,请使用该提案或邮件列表。

(我知道在这种情况下,提案 B 已被锁定;该提案在不到一天的时间内有 77 条评论这一事实说明了原因。这种级别的讨论在邮件列表上比在问题跟踪器上更有效。)

@ianlancetaylor

(我同意可以回复消息;我说这很尴尬,并非不可能。我关于线程的观点仍然存在,因为这个迷你线程在其他评论的暴风雪中消失了。)

说得好,说得有道理。 邮件列表很棒,但我个人发现在这种情况下通过 GitHub 做出贡献更容易。 除了当前的错误处理很棒之外,我没有什么可说的,我希望它保持不变。 表情符号/投票对此非常有用。 您可能不希望 100 个人在邮件列表中写下“请单独处理错误”,而 100 个“投票”就足够了。

由于此问题已被锁定,因此无法再使用 Emojis 对其进行“投票”。 这就是为什么我认为这个问题首先是创建的。

一个侧面但相关的,依赖管理没有得到很好的处理。 Dep 工作得很好,并且突然选择了 go mod(形成了看起来的样子)[1]。 我理解这就是创建提案系统的原因。 我只是觉得如果问题被锁定并且我们被告知转到邮件列表,则在这种情况下,提案系统可能无法代表社区。

[1] https://twitter.com/_rsc/status/1022588240501661696

编辑:Go 团队和社区在听取社区反馈的大部分工作中做得非常出色。 我很欣赏其中的所有工作。 Go 调查就是一个很好的例子。

@sanbornm

Dep 工作得很好

这里需要不同意。 Go 模块最终通过https://proxy.golang.org的持久缓存解决了那个鲜为人知的“gobindata”问题

那个家伙甚至没有意识到这个问题,而是通过 VCS 玩弄花哨的“确保”。

@sirkon这有点离题,但如果您像 Dep 那样的供应商 deps,则不需要任何这些。

就目前而言,我认为我宁愿保持原样,除非添加更多约束,例如每行 1 个 try 语句。 原因是考虑提案中的这个例子 - 它似乎无害info := try(try(os.Open(file)).Stat())但它泄漏文件句柄超出正常控制流的范围。 我认为我们会看到io.Closer实现或其他清理功能导致文件资源泄漏的增加,人们可能会为了追求更紧凑的代码而避开这些功能。

也许有些人会认为这是无关紧要的,因为 f 将不再有效,因此有资格立即进行 GC,并且在某些时候终结器将确保 f 关闭。 我认为它改变了以前明确的(支持 linter 的)今天使用 defer 的约定,这些约定绑定到一个功能块。 当功能块退出时,资源被释放。 依赖垃圾收集器并不能保证您不会耗尽资源(典型的打开文件句柄限制默认值可以在 1k 到 4k 之间)——这很容易被简单的 filepath.Walk 超出,它不会关闭它统计的文件。

总之,我认为这种实现的语法在 Go 中的资源管理中提供了一个微妙的风险,因为它缺少 ctor/dtor 并且依赖于远离代码块的较低级别的 GC 机制来防止资源泄漏。 将一些看起来无害的东西变成潜在的错误情况(打开的文件太多)。

var n int
for _, name in try(os.Readdir(...)) {
   n += try(getSize(name))
}
func getSize(name string) (int, error) {
   return try(try(os.Open(name)).Stat()).Size
}

编辑:
对于约束,我实际上认为如果它只在赋值的右侧有效,那比每行说 1 更好,因为a, b := try(get("a")), try(get("b"))已经足够合理了。 但它仍然保留了执行try(os.Open(name)).Stat() ——如果你要使 try() 无效,但只有当不在任务的 RHS 上时,你才会得到一些不太像 at 的功能全部。

@cstockton哇,很棒的发现!

Rust 实际上有类似的宏(如果我没记错的话是? ),它完全符合try意思,但是它们在那里有适当的 raii,所以这不是该语言的问题,而且是一个巨大的我们案例中的洞

@sanbornm是的,在你的

Finishing Touch

由于有人提到了一个实用程序,它可以计算try可以节省我的时间/工作量的地方的数量,我决定在我最大的工作项目上运行它,加上我旧的 GOPATH GitHub 目录中的所有其他内容。

| 项目 | 位置* | 尝试候选人|
|-----------|------|----------------|
| 呼叫 1 | 2047 | 3 |
| 泵1 | 1030 | 0 |
| 文档1 | 4576 | 8 |
| 胡古泰尔 | 604 | 1 |
| 其他一切 | 8452 | 23 |

  • 根据cloc实用程序,仅 Go 代码,不包括注释。

请记住,“其他一切”的内容包括我在学习 Go 时编写的快速技巧和代码。

我的总体结论是,至少对我来说, try提案无助于将我的错误处理简化到任何有价值的程度。

我喜欢 go 的最大原因是,它的规范将编码人员限制为其他语言可用的一小部分语法。 因为它是一个很小的特征集,所以很容易学习整个特征集。 未来的开发人员可能会查看我的代码并知道我做了什么。 添加到语言中的每个新事物都会降低未来开发人员知道该事物的机会。 滑坡的极端是一种语言,其复杂性使其难以理解,例如 C++ 或 scala。
我不希望在 go 1 中看到任何语法添加。而是将它们放在 go 2 中。

@miekg请将此链接https://github.com/golang/go/issues/32825#issuecomment -506882164 添加到提案中。 这个例子完全否定了这个最近的try关键字的整个想法。

image

我完全同意保持原样。 它有点过于冗长,但很容易理解。

如果我能减少

if err := foo.x(a, b); err != nil {
  return err
}

if err := foo.y(); err != nil {
  return err
}

if err := foo.z(c); err != nil {
  return err
}

if err := check foo.x(a, b), foo.y(), foo.z(c); err != nil {
  return err
}

恕我直言,如果不改变习语太多,也许也会很棒。

如果您在谈论“可能”类型,则它首先需要变体类型。

让我们保持if err != nil它有效,很明显,它真的没有那么冗长,并且在代码流中是有意义的。 当您阅读带有此结构的代码时,您就知道它要做什么。
让我们保持它,我们不要添加try

当我阅读代码时,我希望完成工作的行清晰可读,没有或有最少的错误处理内容。

同级别的 3 个字母 'err' 对我来说是可以接受的。 我不喜欢一些包含重要代码的“检查”函数,因为重要的代码会在第二层(还记得 lisp 吗?),而且我不喜欢之前的“尝试”一行,因为重要的代码会缩进并在第二行。

资源,错误:= begin_job()
如果错误!= nil {
句柄错误()
}

错误 = continue_job(res)
如果错误!= nil {
句柄错误()
}

使用此代码,您可以通过阅读块的第一行来阅读非错误案例的流程(因为我在必须快速阅读时阅读文档标题)

由于有人提到了一个实用程序,它可以计算try可以节省我的时间/工作量的地方的数量,我决定在我最大的工作项目上运行它,加上我旧的 GOPATH GitHub 目录中的所有其他内容。

项目 LOC* 尝试候选人
电话 1 2047 3
泵 1 1030 0
文档 1 4576 8
雨果 604 1
其他一切 8452 23

  • 根据cloc实用程序,仅 Go 代码,不包括注释。

我认为try在较大的程序中更需要。 只是从内存中提取,我认为 LOC 大小在 15-20k 及以上的程序更需要它们,因为那时您可能开始获得只需要传递错误的层,因为它们在封闭系统中被恰当地指定和处理发送方和接收方。 这在很大程度上取决于它是哪种程序。 我可能也不会在较小的程序中使用太多尝试

我认为在较大的程序中更需要尝试。

好点子。 我在 heptio/contour 上尝试了 tryhard,28.7k 行源文本,tryhard 找到了 12 个替换。

我认为在较大的程序中更需要尝试。

好点子。 我在 heptio/contour 上尝试了 tryhard,28.7k 行源文本,tryhard 找到了 12 个替换。

哇! 12 vs 28.7K 行,这真的需要专门的关键字!

好吧,我更感兴趣的是你POV

stat := try(try(os.Open(fileName)).Stat())

我认为如果您的程序更加单一并且不是许多服务之间的服务集成的一部分,则更常见。 当我只是在该存储库 ( heptio/contour ) 的 github 中搜索fmt.errorferrors ,结果很少,因此很难快速概览.. 但正如我说它可能因程序而异,即使对于较大的程序也是如此。

假设您有一个不使用大量外部库的程序。 然后你可以有一个特定的 AuthorizationError (并且你知道返回的所有错误都足够具体,并且任何已经处理和包装的 io 错误)已经包含你的用户元数据并且可以传播而无需更改几层,而不会对实际需要的东西进行太多更改处理它们直到请求层。

我认为如果您的程序更加单一并且不是许多服务之间的服务集成的一部分,则更常见。 当我只是在该存储库的 github 中搜索fmt.errorferrors ,结果很少,因此很难快速概览.. 但正如我所说,它可能因程序而异编程,即使是更大的程序。

假设您有一个不使用大量外部库的程序。 然后,您可以拥有一个特定的 AuthorizationError,它已经包含您的用户元数据,并且可以在不更改几个层的情况下传播,而不会对实际需要处理它们的事情进行太多更改,直到请求层。

你不明白。 注释是为了方便地找到错误发生的方式。 我们同样有os.NotExist但这几乎不是错误路径的一个很好的提示。

@thomasf这里是另一个数据点,来自 juju/juju 几年前的工作副本,

529628 行源代码,尝试找到 1763 (0.3%) 个替换。

好,当然。 由于您已经参与了两者,因此它们可能不是编写程序的不同方法的好例子。 我什至没有时间尝试 tryhard 程序 atm,更不用说在不同来源上正确运行它(无论如何可能无法收集,因为如果通过 github 收集它会省略封闭的源代码)

529628 行源代码,尝试找到 1763 (0.3%) 个替换。

正如某人(需要引用)明智地说的那样, try并没有使处理错误变得更容易。 它使处理它们变得更容易。

如果您分析代码并发现很多try替换,则只能告诉您该代码除了返回错误之外没有对错误执行任何操作。 那可能不是很好的代码。 我们是否应该让人们更容易变得懒惰而不用担心错误? 这难道不是 Go 没有例外的原因之一吗?

我不会对此采取任何立场。 然而,我发现的是他们很少的支持证据表明

一种。 有很多地方try适用于现有的 Go 代码库
湾根据我自己的测量和围棋团队提出的数字,错误处理通常构成 SLOC 的重要部分,参考https://youtu.be/RIvL2ONhFBI?t=440 timecode 07:26

如果您分析代码并发现许多try替换,则只能告诉您该代码除了返回错误之外没有对错误执行任何操作。 那可能不是很好的代码。 我们是否应该让人们更容易变得懒惰而不用担心错误? 这难道不是 Go 没有例外的原因之一吗?

  1. 你正在对你一无所知的理论代码进行价值判断,这不是一个好习惯。
  2. 我不明白try比我们现在拥有的更容易被滥用,go 没有强制执行错误处理的功能,并且已经很容易跳过它。 对我来说try是为了让代码更容易阅读,如果它不需要处理错误。

也许不需要try因为在很多地方都不需要它,但是让我们接受这个想法并提出用例而不是仅仅对它发脾气......

我自己想不出很多实际情况我会如何使用它但它也不存在所以很难说如果它确实存在我是否会设计一些不同的东西..我想到的一件事是我可能do 是使用 try 将许多操作分组到这样的匿名函数中,并在将错误返回给调用者之前仍然处理错误。 它可以使一些代码更具可读性。

var v1, v3 string
if err := func() error {
    try(onething())
    v = try(twothing())
    try(otherthing())
    v3 = try(somethingg())
}(); err != nil {
  ... handle error...
}

我认为此时编写一个网站来将tryhard数据保存在不同的包上并可视化它们可能是一个好主意。 也许修改一点 golang/gddo (godoc.org) 可以完成这项工作。

我更喜欢单独留下if err != nil 。 但是如果我们必须为错误处理添加一些东西,这里有一个新的提议,为它添加throws关键字。

32852

在不重复这里已经列出的一些论点的情况下,我赞同保留if err != nil原样的观点。

我可以提供的观点是:作为教过数百名新手(包括编程和其他语言的 Go)的人, if err != nil对他们来说从来都不是问题。 我工作室中经验丰富的程序员一开始觉得这很不寻常,但很快就会爱上 Go 中错误处理的明确性质。

我们可以在语言中解决更大的问题,社区对此问题的明确反应表明if err != nil不是其中之一。

Go 是完美的,原因有很多。 其中最主要的是“if err != nil”。 这可能看起来很冗长,但对于学习编码的人来说,它可以更轻松地调试代码和更正代码。

@davecheney

我不会对此采取任何立场。 然而,我发现的是他们很少的支持证据表明

一种。 有很多地方try适用于现有的 Go 代码库
湾根据我自己的测量和围棋团队提出的数字,错误处理通常构成 SLOC 的重要部分,参考https://youtu.be/RIvL2ONhFBI?t=440 timecode 07:26

恐怕在当前的气候下,我们发现的任何示例都会被视为“那可能不是好代码”而被驳回。

下面是一个例子:

llorllale:~/go/src/github.com/hyperledger/fabric$ cloc --exclude-dir=vendor .
    2406 text files.
    2256 unique files.                                          
    3130 files ignored.

http://cloc.sourceforge.net v 1.60  T=6.69 s (272.8 files/s, 58350.9 lines/s)
--------------------------------------------------------------------------------
Language                      files          blank        comment           code
--------------------------------------------------------------------------------
Go                             1751          54365          34149         294005
YAML                             35            547           2171           2060
Bourne Shell                     26            354            325           1312
make                              3            135             96            418
CSS                               1             40             14            140
HTML                              3              7              5             63
Python                            1             50            103             57
Bourne Again Shell                1              1              6             50
Java                              3              7              4             26
XML                               2              1              4              2
--------------------------------------------------------------------------------
SUM:                           1826          55507          36877         298133
--------------------------------------------------------------------------------
llorllale:~/go/src/github.com/hyperledger/fabric$ tryhard -l . | grep -v vendor | less | wc -l
1417

公平地说,有关 tryhard 找到的位置数量的数据可能会被需要包装错误的约定混淆。 例如,如果您的公司惯例是

if err != nil {
   return errors.Wrap(err) 
} 

...
if err != nil {
   return errgo.Notef(err, "error doing x") 
} 

这不会被 tryhard 报告。

我们公司有这样的会议。 做一个简单的搜索和替换将那些恢复到裸错误返回给我这些结果:

Language                             files          blank        comment           code
---------------------------------------------------------------------------------------
Go                                    2488          40317          15901         297038

tryhard 报告了 2736 次替换,但对剩余的包装进行人工审查似乎少了大约 1850 次,所以我估计在我们的 30 万行代码库中总共有大约 4500 次try使用。

(就我个人而言,我赞成当前的错误处理的明确性并且不介意。)

例如,如果您的公司惯例是
[用自定义消息包装错误]
这不会被 tryhard 报告。

这就是重点—— try提案仅简化了if err != nil return err裸返回,它不支持使用自定义消息和上下文包装错误。

我相信if err != nil的唯一重复性可以通过还必须指定其他返回值的零值来解决。 可以更新语言以消除这种情况。 例如:

在今天的 Go 中,如果我有一个带有这个签名的函数:

func add(x, y string) (int, error)

在函数的某个地方,我必须写:

func add(x, y string) (int, error) {
    // ...
    if err != nil {
        return 0, err
    }

强制编写器在整个函数中重复相同的零值。

如果语言可以自动填充非错误返回值的零值,那将会容易得多(并且错误冗长和可读性的成本很小):

func add(x, y string) (int, error) {
    // ...
    if err != nil {
        return ..., err
    }
    // ...
}
func main() {
    add("8", "beep") // returns 0, error(`strconv.ParseInt: parsing "beep": invalid syntax`)
}

我可以从大量与数据库查询和调用交互的代码的经验中说,必须在函数中重复零值是 Go 风格错误处理的唯一缺点。 否则我同意这个提议的观点:别管if err != nil

注意:是的,命名返回值可以_sort of_完成此操作(https://play.golang.org/p/MLV8Y52HUBY),但是在使用这种技术在我自己的代码库中实现了一些功能后,我被提醒了多少英尺-gun 命名的返回值是; 我总是最终掩盖了命名的返回值。

例如,如果您的公司惯例是
[用自定义消息包装错误]
这不会被 tryhard 报告。

这就是重点—— try提案仅简化了if err != nil return err裸返回,它不支持使用自定义消息和上下文包装错误。

确实如此,我正在考虑允许添加描述性字符串的变体。 我们的错误返回的绝大多数(~4000 / 4500)是无上下文的errgo.Mask(err) ,我认为这相当于无描述try() ,但目前它会减少功能,因为 errgo 添加了堆栈信息,而 try 没有(还)。

@ianlancetaylor这里有一个提议。 @miekg建议您作为我们语言的领导者之一,不再追求用其他一些与原始 Go 作者决定的错误处理精神相矛盾的结构来替换if err != nil 。 就我个人而言,感觉就像你试图通过将其移至golang-nuts而不是像我们的其他提案一样对待它来断言这个问题的不重要。 这可能不是你的意图,但这是我感受到的影响。

我们的错误处理方法是独一无二的,我相信它比其他语言具有巨大的附加价值。 它彻底改变了我对我构建的系统中错误的看法,因此我成为了一名更强大的软件工程师。 我不希望我们为了吸引更多 Go 开发人员而迎合吵闹的少数人或局外人。 我认为我们应该对某些事情采取强硬态度,我们选择处理错误的方式就是其中之一,因为它最终通过牺牲代码的简洁性使我们变得更好。

这是谷歌内部团队与社区建立更多信任和信心的机会,或者继续我们目前所处的对语言、生态系统或其用户不利的轨迹。

我要求 Go 团队按原样接受这个提议,同时继续追求其他不相关的语言迭代,这些迭代具有更明确的附加价值。

跟踪器可能没有线程,但我个人更愿意保证该提议以官方身份得到回应,而不是归入谷歌小组,在那里它可以悄悄地淡出人们的视线。

这个话题也已经在谷歌组上讨论过。

#32437 的当前版本并不令人满意。 try() 内置函数向未经训练的眼睛隐藏了许多执行路径。 带有 check 和 handle 的原始提案非常容易理解,并且 check 关键字很突出。

现在,try() 内置函数看起来像一个函数——它可以改变控制流并不明显。 我们也有 panic(),但它(我相信)总是独立存在,有一个显眼的名字并且它的使用很少。 另一方面,try() 可以隐藏在复杂的表达式中。

@teckman Robert 与 Rob 和 Ken 设计了 ​​Go 的第一次迭代,并且 Robert 和 Russ 很早就加入了团队。 他们从一开始就一直在研究 Go。 我认为我们可以相信他们知道提案是否“与原始 Go 作者决定的错误处理精神相矛盾”。

我不喜欢像今天这样冻结错误处理的提案的原则。 这样的提案将禁止所有关于该主题的未来提案。

为什么不接受迭代设计呢? 我们有检查/处理建议。 但讨论了一些缺点。 这导致了 try 提议。 现在讨论这个提议的一些缺点。 也许这会导致另一个更好的提议,直到找到正确的方法。

我们的错误处理方法是独一无二的

Rust 中的错误处理在概念上类似于我们在 Go 中所做的(错误是值,显式控制流,除了当它们使用 sum 类型时我们使用多个返回值)。 Rust 有与 Go 相同的问题,但有详细的错误处理。 这导致 Rust 添加了 try! 宏,最终? 操作员。 我会说 Rust 社区在错误处理方面比 Go 社区更严格(错误处理 RFC 和讨论很有启发性)。 他们找到了一种方法来减少错误处理的冗长性,而不会出现错误处理的滑坡。 我相信我们也可以。

我们目前所处的轨迹对语言、生态系统或其用户不利

你在说什么? Go 正在不断改进。 能够免费访问如此出色的语言、工具和文档(如言论自由)真是太棒了。

@teckman Robert 与 Rob 和 Ken 设计了 ​​Go 的第一次迭代,并且 Robert 和 Russ 很早就加入了团队。 他们从一开始就一直在研究 Go。 我认为我们可以相信他们知道提案是否“与原始 Go 作者决定的错误处理精神相矛盾”。

我不喜欢像今天这样冻结错误处理的提案的原则。 这样的提案将禁止所有关于该主题的未来提案。

为什么不接受迭代设计呢? 我们有检查/处理建议。 但讨论了一些缺点。 这导致了 try 提议。 现在讨论这个提议的一些缺点。 也许这会导致另一个更好的提议,直到找到正确的方法。

我们的错误处理方法是独一无二的

Rust 中的错误处理在概念上类似于我们在 Go 中所做的(错误是值,显式控制流,除了当它们使用 sum 类型时我们使用多个返回值)。 Rust 有与 Go 相同的问题,但有详细的错误处理。 这导致 Rust 添加了 try! 宏,最终? 操作员。 我会说 Rust 社区在错误处理方面比 Go 社区更严格(错误处理 RFC 和讨论很有启发性)。 他们找到了一种方法来减少错误处理的冗长性,而不会出现错误处理的滑坡。 我相信我们也可以。

我们目前所处的轨迹对语言、生态系统或其用户不利

你在说什么? Go 正在不断改进。 能够免费访问如此出色的语言、工具和文档(如言论自由)真是太棒了。

Rust 的发展历史表明,它背后的人并不知道他们在做什么。 他们基本上复制了 Haskell 的错误处理原则,但这些原则与命令式(现实世界?)编程不太匹配。 他们的?宏只是最初失败的错误处理系统的一种解决方法。

@ianlancetaylor这里有一个提议。 @miekg建议您作为我们语言的领导者之一,不再追求用其他一些与原始 Go 作者决定的错误处理精神相矛盾的结构来替换if err != nil 。 就我个人而言,感觉就像你试图通过将其移至golang-nuts而不是像我们的其他提案一样对待它来断言这个问题的不重要。 这可能不是你的意图,但这是我感受到的影响。

我们的错误处理方法是独一无二的,我相信它比其他语言具有巨大的附加价值。 它彻底改变了我对我构建的系统中错误的看法,因此我成为了一名更强大的软件工程师。 我不希望我们为了吸引更多 Go 开发人员而迎合吵闹的少数人或局外人。 我认为我们应该对某些事情采取强硬态度,我们选择处理错误的方式就是其中之一,因为它最终通过牺牲代码的简洁性使我们变得更好。

这是谷歌内部团队与社区建立更多信任和信心的机会,或者继续我们目前所处的对语言、生态系统或其用户不利的轨迹。

我要求 Go 团队按原样接受这个提议,同时继续追求其他不相关的语言迭代,这些迭代具有更明确的附加价值。

他们不能对 60 年代的当前类型系统做任何严肃的事情。 他们最终需要在 Go 2.0 中借用 80 年代的想法

你在说什么? Go 正在不断改进。 能够免费访问如此出色的语言、工具和文档(如言论自由)真是太棒了。

@ngrilly最后一部分可能是为了更广泛的讨论。 在不破坏该提案的情况下,但要结束该评论,用户和社区/生态系统中的领导层之间存在越来越多的不一致情绪。

在接下来的讨论中,我不认为在语法中增加更多的认知开销是一种胜利。 我很高兴他们找到了对他们有用的东西,我们不是他们。

我刚刚为内联 if 语句打开了一个提案: https :

参考: https :

如果每个人都提交关于他们非常喜欢拥有的任何新的 golang 2.0 功能的提案,并且还会提供他们的https://github.com/golang/go分支的一个分支(以及任何其他需要的存储库)实现该提案。

你不同意吗?

@av86743似乎超出了本提案的范围。 请提交一份建议书,建议采取该行动。

我确实看到了一些挑战,例如在有人根据提案文件本身的某些内容拒绝之前,可能会浪费大量精力。 然后你把所有的时间都花在了一个甚至不会被审查的分叉上。

这个语法怎么样:

# call error handler
try callFunction(), errorHandler()

# error handler with anonymous function
variable := try callSomething(), func(err *Error) { # error handling }

@teckman如果我建议将这个讨论转移到其他地方,这似乎不重要,我深表歉意。 我在请求中解释了我的理由,我相信它们仍然有效。 Go 团队考虑邮件列表讨论和提案。

既然你提到了“Go 的原作者”,我认为值得指出的是,“try”的提议是由@griesemer提出的,他是 Go 的三位原作者之一。

我非常同意这个提议,我认为唯一需要改变的就是 go fmt,make go fmt 允许一行 if 语句。

我真的想要一行

if err != nil { return wrappedErr{err} }

而不是三行

if err != nil {
    return wrappedErr{err}
}

@av86743似乎超出了本提案的范围。 请提交一份建议书,建议采取该行动。

@theckman你是在告诉我该怎么做,这不仅不礼貌,而且表面上很粗鲁。 您可以尝试以任何您选择的方式定位自己,但是当您这么说时,无论是我自己还是这里的其他任何人都不是您的“去取”猴子。

我确实看到了一些挑战,例如在有人根据提案文件本身的某些内容拒绝之前,可能会浪费大量精力。 然后你把所有的时间都花在了一个甚至不会被审查的分叉上。

对于[... _为简洁起见省略的完全适当语言的描述_ ...],这只会是“浪费精力”。

但是,对于编码人员来说,这将是一项微不足道但有用的练习,同时也是对 Go 社区的一项服务。

@av86743我认为你提出的想法很有趣,我不希望它作为无关问题的评论而丢失。 如果您没有兴趣以官方身份进行审议,我很抱歉主张您提出单独的问题。

尽管这个具体的提议来自@griesemer ,但我发现很难相信他十年来一直对 Go 中未包装的错误返回的冗长

值得一提的是,我个人怀疑围棋团队对此的推理过程如下:

  1. Go 非常流行和广泛使用,因此成千上万的人自然会对它提出意见、建议和抱怨。
  2. 你只能石墙这么久。 Go 团队承受着巨大的压力,需要对来自社区的所有噪音做_某事_。
  3. 这是东西。

如果尝试发现了像recover()这样的错误,那么在defer中添加一个catch()函数来捕获如何。
例子:

func doSomthing()(err error){
    //return error
}

func main(){
    defer func(){
        if err:=catch();err!=nil{
            //found error
        }
    }()

    try doSomthing()
}

在许多功能上

func Foo() (err error) {
    defer func(){
        if err:=catch();err!=nil{
            //found error
        }
    }()

   try{
    file1 := open("file1")
    defer file1.Close()
    file2 := open("file2")
    defer file2.Close()
   }
   //without try
    file3,err := open("file3")
    defer file3.Close()
 }

如果尝试发现了像recover()这样的错误,那么在defer中添加一个catch()函数来捕获如何。
例子:

func doSomthing()(err error){
    //return error
}

func main(){
    defer func(){
        if err:=catch();err!=nil{
            //found error
        }
    }()

    try doSomthing()
}

在许多功能上

func Foo() (err error) {
    defer func(){
        if err:=catch();err!=nil{
            //found error
        }
    }()

   try{
  file1 := open("file1")
  defer file1.Close()
  file2 := open("file2")
  defer file2.Close()
   }
   //without try
    file3,err := open("file3")
    defer file3.Close()
 }

这如何帮助以单独的方式处理每个错误?

一些澄清:

  1. try提案既没有引入新的语法也没有引入新的关键字,这与一些人在这个线程上声称的相反。 它只是引入了一个新的内置功能,关于添加新功能可能能够做的最小更改。 讨论这个时请务必准确,因为这很重要。 添加新语法和新关键字与内置关键字之间存在巨大差异。 前者是一个重大变化,后者是一个相对较小的补充。 try提案建议的是一个相对较小的添加。

  2. 我同意@ianlancetaylor 的观点,即这个讨论最好在别处举行(golang-nuts)。 这里没有建议。

  3. 确实, @bitfield ,我对Go 中未包装错误返回的冗长程度的“内心愤怒”为零,谢谢:-) 但我确实认为错误检查可能比必要的更冗长; 社区反复提出同样的情绪这一事实清楚地表明,我们(围棋团队)并不孤单。 我不会说做“某事”有很大的压力——我们已经断断续续地研究了很长时间,我们很满足于等待“正确”的方法.

try提案是我们找到的解决错误检查问题的最小解决方案(在社区贡献的大力帮助下)。 try提案非常明确地指出,如果每个错误测试都需要以某种特定方式处理错误,它_将毫无帮助_。 try仅在以相同方式测试和处理函数中的所有错误(然后我们建议使用defer )或仅返回它们时才有帮助。 这里很难说得更清楚,但让我们重复一下提案所说的内容try不会在所有错误情况下都有帮助。 它在很多情况下都有帮助。 对于其他所有内容,请使用if语句。

@griesemer try 太容易出错:Go 中没有 RAII,因此在许多情况下我们不能只保留该函数。

@sirkon ,我不确定 RAII 与本次讨论有何关联。 tryif ..., err := f(); err != nil { return ..., err }现有模式替换为... := try(f()) 。 如果使用try存在资源释放错误,那么它肯定也事先存在。 try的引入既不会增强也不会阻止资源释放错误。

@sirkon ,我不确定 RAII 与本次讨论有何关联。 tryif ..., err := f(); err != nil { return ..., err }现有模式替换为... := try(f()) 。 如果使用try存在资源释放错误,那么它肯定也事先存在。 try的引入既不会增强也不会阻止资源释放错误。

阅读线程,有一个例子:

info := try(try(os.Open(fileName)).Stat())

@sirkon我已经多次看到这个例子了。 我同意这很有趣。 但让我们再考虑一下。 建议的try内置函数基本上是 Go 代码中某种样板的语法糖。 所以我们可以将该示例转换为原始代码。

    f, err := os.Open(fileName)
    if err != nil {
        return err
    }
    info, err := f.Stat()
    if err != nil {
        return err
    }

该代码具有相同的错误。 我当然见过这样的代码。 对我来说,内建的try使该错误更易于编写或更难查看,这一点并不明显。

[看起来@ianlancetaylor只是打败了我。]

@sirkon这个错误已经是可能的, try与否 - Go 不会阻止你编写错误的代码。 或者反过来,使用错误代码作为不应允许try的原因并不是一个令人信服的论点。 相反, go vet应该标记有问题的案例。

defer是在函数返回时清理的 Go 习惯用法,而且效果很好。 这里的正确方法当然是:

f := try(os.Open(filename))
defer f.Close()
info := try(f.Stat())

将此与:

f, err := os.Open(filename)
if err != nil {
   return ..., err
}
defer f.Close()
info, err := f.Stat()
if err != nil {
   return ..., err
}

使用try源专注于主要关注点:获取文件的文件信息。 使用传统方法,大部分源代码关注可能的错误; 而且都是一样的。 即使我们想要修饰错误, try方法也能很好地工作:

defer errd.Wrap(&err, "failed to do X for %s", filename)
f := try(os.Open(filename))
defer f.Close()
info := try(f.Stat())

使用类似errd包的东西(参见问题 #32676)。

@griesemer
我未来的自我代码审查仍然不断尖叫控制流机制应该在它自己的线上。 这种方法在当前提案中是否有效(不强制执行)? 除了可读性之外,还简化了对更详细的错误处理逻辑的重构。

defer errd.Wrap(&err, "failed to do X for %s", filename)
f, err:= os.Open(filename)
try(err) // check is so much a better term
defer f.Close()
info, err := f.Stat()
try(err)

此外,这种handle的方法在这里看起来很棒,但是多个延迟不会弄得一团糟吗? 或者会有一个函数范围的sync.Once之类的东西(我没有在错误问题中看到澄清)? 如果是这样,匿名函数会被授予自己的作用域吗? 和阴影... eesh - 谁在第一个,什么在第二个?

这一切感觉就像最终会有两种编写 Go 代码的“模式”。 说不是这样。

@griesemer虽然您说得对,但今天也可能出现该错误,但我强烈认为,随着当前的 try 实现,将来它会变得更加普遍。 有人来自几乎_任何_流行的语言,我可以().think.Of(Has)Chaining().Of(Methods) 无论好坏都根深蒂固。 这些语言都提供了自己的习语,使模式自然而安全。 他们在 Go 中遇到了直接的障碍,因为类型系统迫使他们分配带有伴随失败条件的每个值,根本没有合理的模式可以避免这种情况(或者 try 提议将不存在)。

如果这个提议被接受,他们将有办法避免if err样板,允许他们以他们熟悉的方式编写代码。 除了在创建 try 之前编写的 stackoverflow 答案、博客文章等中,他们将拥有近十年的 Go 代码。 他们将很快学会使用 try 简单地删除 err 和 if 语句。 他们想要一个文件大小,他们可以粘贴包含在try代码,直到他们可以访问想要的字段,就像 try 提议中的Stat() 。 这是他们习惯的模式,因此他们在编写 Go 时应用它是很自然的。 鉴于最具针对性的 Go OS 将所有内容都视为文件,因此可以假设会发生更多资源泄漏。

这让我明白为什么我强烈不同意“你今天已经可以做到这一点”的说法——因为你根本做不到。 当然 - 你可以泄漏文件句柄。 但是 Go 并没有给程序员机会跳过在作用域中使用标识符从而也泄漏文件。 跳过的每个标识符f是一个文件句柄泄露。 _requires_Go 生态系统中某些突出的习语被破坏的功能的使用。 因此,引入今天设计的功能显然会增加 Go 中泄漏资源的风险。

这就是说,正如我在https://github.com/golang/go/issues/32825#issuecomment -506882164 中提到的,如果进行一些小的调整,我实际上会支持try ,我认为更改将是欢迎补充语言。 我认为 try 需要的只是使其仅在赋值的 RHS 上有效,并且不允许返回值可寻址。 使 try 用法的“好”示例(往往每行一次 try)成为使用 try 的“唯一”方式,即:

info := try(try(os.Open(filename)).Stat()) // compiler error
f := try(os.Open(filename)) // valid
// we were forced to assign f, so we still have an identifier to Close (serve linters and users alike)
defer f.Close()
info := try(f.Stat())
a, b := try(strconv.Atoi("1")), try(strconv.Atoi("2")) // also valid 
a, b := try(strconv.Atoi("1"), strconv.Atoi("2")) // maybe?
a, b := try strconv.Atoi("1"), strconv.Atoi("2")

我认为这自然会更适合语言,保持try所有当前好处(除了嵌套它们,如果你认为这是一个好处),没有任何缺点。 我不认为 try 的嵌套对任何人有任何好处,它节省的很少,但为滥用提供了无限的可能性。 我今天并没有感到特别邪恶,所以这是我能做的最好的事情:

total := try(try(os.Open(filename)).Stat()).Size() + try(strconv.Atoi(try(ioutil.ReadAll(os.Stdin))))

但是_我们_会想到最坏的情况,如果你让我们。

@davedtry(err)放在第二行完全支持现有的try提案: try只是想要一个参数,该参数计算为一个或多个值,其中最后一个值为输入error ,写try(err)自然就满足

我不确定我是否关注您对defer ——如果需要不同的处理程序, defer可能不是正确的选择; 相反,可能需要传统的if (如提案中所述)。

@cstockton我同意嵌套的try可能会很成问题; 但我也相信如果我们有try ,大部分代码看起来就像你给出的(有效)

就风格而言,我们没有在语言中加入诸如您喜欢的限制之类的限制 - 我们为此使用了go vet 。 最后,对于编写的软件来说,效果是一样的。 但是,由于语言中没有它,我们并没有束缚自己。 使这些限制恰到好处是很棘手的,它们使规范变得不必要地复杂。 调整go vet简单得多,而且当我们学到的比调整语言更多时,它会变得更聪明。

@griesemer感谢您的澄清。 在代码示例中,如果第一行是var err error ,换行是否可能会影响两个已检查的错误? 我已经看到有关阴影是将来可能会处理的问题的讨论。 这与此有何/可能有关?

如果尝试发现了像recover()这样的错误,那么在defer中添加一个catch()函数来捕获如何。
例子:

func doSomthing()(err error){
    //return error
}

func main(){
    defer func(){
        if err:=catch();err!=nil{
            //found error
        }
    }()

    try doSomthing()
}

在许多功能上

func Foo() (err error) {
    defer func(){
        if err:=catch();err!=nil{
            //found error
        }
    }()

   try{
    file1 := open("file1")
    defer file1.Close()
    file2 := open("file2")
    defer file2.Close()
   }
   //without try
    file3,err := open("file3")
    defer file3.Close()
 }

这如何帮助以单独的方式处理每个错误?

像其他用户提交的一样

func Foo() (err error) {
    defer func(){
        if err:=catch();err!=nil{
            //found error
        }
    }()

    file1 :=try open("file1")
    defer file1.Close()
    file2 :=try open("file2")
    defer file2.Close()

        //without try
       file3,err := open("file3")
       defer file3.Close()
 }

@daved在这些示例中,假设err是结果errortry将始终设置该结果错误变量,无论名称(或缺少名称)如何。 如果您有一个名为err的局部变量,那么这是一个不同的变量。 如果要引用结果错误,则必须使用不同的名称。 请注意,这已经是无效的:

func f(...) (..., err error) {
   var err error // << err already declared
   ...

另一方面,如果你写:

func f(...) (..., err error) {
   a, b, ... err := g() // redeclaration of err
   ...

赋值中的err与结果参数列表中命名的相同。 这里的情况与很长一段时间以来的情况没有什么不同。

PS:我们可能应该停止劫持这个问题进行try讨论并回到最初的提案 - 它将在明天(7 月 1 日)解锁并再次开放供讨论。

@godcong catch()函数(或类似函数)只允许您获取错误,而不是设置它(通常人们希望在作为错误处理程序运行的延迟函数中设置封闭函数的错误) . 可以通过使catch()返回*error来使其工作,该try提案。

另外,请参阅上面的 PS。

@griesemer

在这里很难更明确,但让我们重复一下提案所说的内容:尝试在所有错误情况下都无济于事。 它在很多情况下都有帮助。 对于其他所有内容,请使用 if 语句。

我认为这正是try()提案的致命缺陷:以前只有一种方法可以进行错误检查,现在将有两种,在代码库中混杂在一起。 此外,至少对于我正在处理的代码库,不到 20% 的if err != nil可以替换为try() ,虽然不是微不足道,但似乎不足以创建一个拆分错误处理样式。

就我个人而言,我更喜欢一个错误处理结构,它足够强大,可以替代 95% 的if err != nil情况。 我想这也是很多人会喜欢的。

@griesemer我同意人们会学习并且工具将是必须的,因为您所指的样式指南、良好实践、示例、文档等都将过时。 我认为很明显,目前提议的 try 引入了编写不正确软件的微妙新方法。 不清楚的是,在人们总是可以编写不正确的软件的前提下,如何驳回这一事实是一个有效的反驳论点?

我将在这里转换角度,嵌套 try 语句的用例是什么,该语句对于我概述的潜在副作用来说足够强大? 今天的 Go 代码如何通过允许可链式嵌套可尝试分隔的括号派对在任何地方疯狂出现而受益? 我的猜测是它没有,我认为没有人要求尝试嵌套,它随提案一起提供,因为它是作为一个函数实现的。 您不想添加任何约束,例如删除嵌套/可寻址以限制嵌套滥用或细微错误,因为这会使语言规范更加复杂。 这里的主题是防止给语言引入复杂性,还是添加更好的方法来处理错误?

因为如果这里的目标是不使语言规范更复杂,那么选择很明确:不要添加具有通用返回和参数的新函数,可以任意嵌套,提供控制流并更改给定值的数量(但只有当它们满足特定的内置接口时),而且可能更多的是我忘记了例如具有前所未有的复杂性的函数。 如果目标是改进错误处理,我认为它应该在不引入产生错误的新方法的情况下做到这一点。

@sirkon我已经多次看到这个例子了。 我同意这很有趣。 但让我们再考虑一下。 建议的try内置函数基本上是 Go 代码中某种样板的语法糖。 所以我们可以将该示例转换为原始代码。

    f, err := os.Open(fileName)
    if err != nil {
        return err
    }
    info, err := f.Stat()
    if err != nil {
        return err
    }

该代码具有相同的错误。 我当然见过这样的代码。 对我来说,内建的try使该错误更易于编写或更难查看,这一点并不明显。

对我来说,“较慢”的传统流程显然会留下更多空间来通知文件应该关闭,而try会引发此类泄漏,因为人们往往更喜欢使用捷径。

@godcong catch()函数(或类似函数)只允许您获取错误,而不是设置它(通常人们希望在作为错误处理程序运行的延迟函数中设置封闭函数的错误) . 可以通过使catch()返回*error来使其工作,该try提案。

另外,请参阅上面的 PS。

Go 的类型系统停留在 60 年代,因此自然无法很好地处理边缘情况。 如果你有足够的远见,借用 80 年代的想法,你就会有方法来控制微妙的、容易出错的流程。 你现在正试图在一个中世纪村庄建造玻璃和金属建筑:糟糕的是,这些中世纪村庄没有电和水管,所以你也不会拥有。

看看在golang/go本身中使用任何新的和改进的错误设施的程度将会很有趣。 如果有的话。

看看go2 fmt是否可以选择输出普通的go1.x也很有趣。

根据我自己的经验,自从我通过以下方式在返回的错误中添加上下文以来:

import "github.com/pkg/errors"
func caller(arg string) error {
  val, err := callee(arg)
  if err != nil {
    return errors.Warpf(err, "failed to do something with %s", arg)
  }

  err = anotherCallee(val)
  if err != nil {
    return errors.Warpf(err, "failed to do something with %s", val)
  }

  return nil
}

对于生产中出现的问题,支持团队很少需要我的意见。

恕我直言,我相信改进错误处理不是为了减少样板代码,而是提供一种更方便的方法来为错误添加上下文。 我仍然找不到使用 try() 的好方法。

也许在延迟中添加上下文:

func caller(arg string) (err error) {
  defer func() {
    switch t := err.(type) {
      case CalleeErr:
        err = errors.Wrapf(err, "failed to do something with %s", arg)
      case AnotherCalleeErr:
        err = errors.Wrapf(err, "failed to do something with %s", val)
    }
  }()

  val := try(callee(arg))
  try(anotherCallee(val)
  return nil
}

似乎并没有节省很多打字,但我们牺牲了可读性和性能。

最终可能会以这种方式使用 try():

func caller(arg string) error {
    val, err := callee(arg)
    try(errors.Warpf(err, "failed to do something with %s", arg))

    err = anotherCallee(val)
    try(errors.Warpf(err, "failed to do something with %s", val))

    return nil
  }

它确实减少了几行,就是这样。

对我来说,这个问题的大多数解决方案似乎打破了我认为与其他使用异常的语言分开的一件事:错误处理机制不用作控制流。 这些解决方案中的大多数都添加了某种形式的控制流(检查/处理、尝试、捕获、期望),此时我认为 Go 团队也可以添加异常并收工。

虽然如果我们能有一个类似于 ruby​​ 的ifreturn的特殊情况,我会很高兴

return err if err != nil

PS请原谅我在语言设计方面的经验不足,如果我说了一些愚蠢的话,请随时指出并教育我。

如果尝试发现了像recover()这样的错误,那么在defer中添加一个catch()函数来捕获如何。
例子:

func doSomthing()(err error){
    //return error
}

func main(){
    defer func(){
        if err:=catch();err!=nil{
            //found error
        }
    }()

    try doSomthing()
}

在许多功能上

func Foo() (err error) {
    defer func(){
        if err:=catch();err!=nil{
            //found error
        }
    }()

   try{
  file1 := open("file1")
  defer file1.Close()
  file2 := open("file2")
  defer file2.Close()
   }
   //without try
    file3,err := open("file3")
    defer file3.Close()
 }

这如何帮助以单独的方式处理每个错误?

像其他用户提交的一样

func Foo() (err error) {
    defer func(){
        if err:=catch();err!=nil{
            //found error
        }
    }()

  file1 :=try open("file1")
  defer file1.Close()
  file2 :=try open("file2")
  defer file2.Close()

        //without try
       file3,err := open("file3")
       defer file3.Close()
 }

在您的示例中,您通过相同的延迟处理所有错误。 如果您想向错误中添加自定义消息和自定义信息,会发生什么情况?

@ianlancetaylor有没有人建议增加“:=”运算符以支持“推断返回” - 基本上与没有函数调用的 try 完全一样。 这将解决我在双方看到的很多问题:

  • try作为该功能的名称一直存在争议,只要我们将其作为一个函数来实现,我们就会被困在给它一个我不确定是否有人会 100% 满意的名称。
  • try做了前所未有的事情,它有像append()这样的输入并影响像panic()这样的控制流,同时取代了一个无处不在的模式( if err != nil ) .
  • try嵌套是决定将其作为功能实现的结果,而不是作为源自严格技术辩论的增值。
  • try被实现为保持向后兼容性的函数

我认为如果我们像做类型一样简单地推断返回值,事情看起来很简洁并且感觉更像是“Go”:

f, err := os.Open(filename)
if err != nil {
   return ..., err
}
defer f.Close()

info, err := f.Stat()
if err != nil {
   return ..., err
}

_在此处插入针对此行为的正确计算机科学描述_,直到通过短变量声明解决推断返回:

f := os.Open(filename)
defer f.Close()
info := f.Stat()

看起来比以下更整洁:

f := try(os.Open(filename))
defer f.Close()
info := try(f.Stat())

这解决了我上面列出的所有问题,同时(在我看来)感觉更像“Go like”并保持向后兼容性。 似乎也更容易解释,考虑到try在所有其他语言中无处不在的含义,对try()函数调用的“隐式”返回感觉真的不合适。 我不能说 100% 的实施,但似乎可以通过大致相同的努力来完成? AssignStmt可以添加一个字段,该字段指定 LHS 上的哪些表达式省略了它们的 err 值以通知与内置函数相同的后端编译?

我喜欢按原样进行错误检查,但如果这确实是一个必须解决的问题,我认为新关键字可能是更好的解决方案。 任何解决方案都可能涉及控制流更改,并且最好使之尽可能明显。

在此示例中,每次设置err时都会评估on条件。

func example() (foo int, err error) {
    on err != nil {
        return foo, err
    }

    foo, err = calcThis()
    foo, err = calcThat(foo)

    return
}

这也可以在不为函数签名中的返回值声明名称的情况下工作。

func example() (*int, error) {
    var err error

    on err != nil {
        return nil, err
    }

    foo, err = calcThis()
    foo, err = calcThat(&foo)

    return &foo, nil
}

这也可以设置多次。 这是一个人为的例子:

func example() (*int, error) {
    var err error

    on err != nil {
        return nil, err
    }

    foo, err = calcThis()

    on err != nil {
        return &foo, err
    }

    foo, err = calcThat(&foo)

    return
}

在我看来,这保留了 Go 的风格和可读性,同时明确了每一步发生的事情(一旦你理解了范式),因为它在每次设置err之后有效地插入了该条件。

您甚至可以执行以下操作:

func example() (foo int, err error) {
    var message string

    on err != nil {
        return foo, errors.Wrap(err, message)
    }

    message = "failed to calc this"
    foo, err = calcThis()

    message = "failed to calc that"
    foo, err = calcThat(foo)

    return
}

最后,这不仅适用于错误处理。 想要在 foo == 0 时返回? 另一个人为的例子:

func example(i int) bool {
    on x == 0 {
        return false
    }

    x = calcSomeInt(i)
    return true
}

@cstockton

我正要抗议以这种方式消除错误有点太容易了,但是:

  • 这已经是一个“陷阱”,函数只返回_only_ 错误,例如json.Unmarshal 。 IME 优秀的代码审查人员很快就会学会注意这一点。
  • 它仍然是不返回错误值的函数中的语法错误,例如http.Handler方法。
  • 如果你知道如何处理错误,你就会考虑它,你会预料到它,并且你可能无论如何都会捕获错误值_以便_你可以处理它。
  • 如果您的意图只是将遇到的任何错误传回以使其成为其他人(调用者的)的问题,那么这样做可以实现这一点,但有一个小缺点,即您错过了为上下文明确添加更多注释的机会。

所以,在权衡了我能想到的所有优点之后,并没有注意到任何明显的缺点,我......在围栏上。 我怀疑有一些我没有考虑过的不明显的缺点。 但我开始喜欢它的发展方向,因为无论对任何人来说都没有什么价值。

我希望这是正确的回应方式,我没有承诺
严重失礼。

在19年7月1日,克里斯·斯托克顿[email protected]写道:

@ianlancetaylor有没有人建议增加“:=”运算符以支持
“推断返回” - 基本上与没有函数的 try 完全一样
称呼。 [...]

在这种情况下,我觉得有趣的是我们有一些东西
类似于“逗号OK”范式,现在省略“错误”
受让人在某些明确定义的情况下是允许的。 值得
注意到,但本身显然不足以使其成为有效的
主张。

卢西奥。

这个错误已经存在,无论尝试与否 - Go 不会阻止您编写错误的代码。 或者反过来,使用糟糕的代码作为不应该允许尝试的原因并不是一个令人信服的论点。 相反,去兽医应该标记有问题的案例。

@griesemer我不同意。 虽然它可以节省击键次数,但指针算法被排除在 Go 之外,前提是它很容易编写糟糕的、损坏的代码。 我觉得这是同一类型的功能,会使错误更难以检测。

data := try(ioutil.ReadAll(try(os.Open("foo.txt"))))

try的典型示例使用了上述示例,对我来说这显然会泄漏文件描述符。 (在当前实现中,Go 在用户不知情的情况下在运行时中寻找并关闭此类描述符的事实可能会给我们带来安慰,但无论如何还有一个更好的例子)。

data := try(ioutil.ReadAll(try(http.Get("http://example.com/")).Body))

上面的代码读起来像正确的代码,但忽略了完整读取响应主体然后在愉快的路径上关闭的要求。 如果您看的时间足够长,错误示例的令人作呕的优雅应该会表明我们将来会看到这些类型的错误。

作为在这一点上进行审查而不是编写代码的人,我希望 Go 不要添加使错误如此诱人的功能。

@jesse-amano 使用赋值运算符实际上阻止了这种情况的发生,如果没有明确的赋值语句,下面的行为与今天完全一样,即:

json.Unmarshal(...)
(http.Handler)(h).ServeHTTP(w, r)

至于只返回错误的值可以像return json.Unmarshal(...)一样返回,并且也可以用更紧凑的形式表示,因为不需要在 if 块之外存在值。

if err := json.Unmarshal(...); err != nIl {
    return err
} 

在这种情况下,我觉得有趣的是,我们有一些类似于“逗号 OK”范式的东西,现在在某些明确定义的情况下,允许省略“错误”受让人。 值得注意的是,但显然这本身不足以使这一命题成为有效的命题。

在没有括号或任意嵌套和链接的情况下尝试的行为将是相同的。 我认为在不破坏语言的情况下很难找到大多数人认为感觉自然的东西。 我承认,由于 Go 的作者似乎非常决心将这种类型的功能添加到 Go 中,因此我真的很深入地寻找替代方案,因为我绝对相信当前形式的try不适合 Go。 这是我能想到的最接近不破坏 BC 的事情,但也许对足够多的人来说仍然感觉不对。 无论哪种方式,我都希望尝试提案被拒绝,或者有人想出更多人可以同意的东西。

编辑:@jesse-amano 我完全错过了你的观点,抱歉! 我想在不返回错误的函数中会显示典型的赋值计数不匹配编译错误? 我想 try 可能会为此引入一种新型的编译器错误消息。

@beoran关于https://github.com/golang/go/issues/32825#issuecomment -507126700 :错误处理已经因情况而异:有时我们返回错误不变,有时我们返回修饰错误,有时我们采取行动发生错误时,有时我们(可以正确地)忽略错误。 他们唯一共享的东西(除非我们忽略错误)是err != nil测试(我们已经可以通过多种方式进行测试)。 尽管新语言特性能够捕获所有这些情况(或其中的 95%)会很好,但这样的构造很可能以非正交方式干扰我们已有的其他控制构造。 也就是说,我们所能希望的最好的事情就是让其中的一些案例(可能是其中的 20%,也许是 50%)变得更好。

@cstockton关于https://github.com/golang/go/issues/32825#issuecomment -507136111:如果嵌套try是唯一剩下的路障, go vet不够好,我认为我们可以考虑禁止嵌套try的 - 这很容易。 但目前我认为我们仍处于 FUD(恐惧、不确定和怀疑)阶段,几乎没有人真正认真地尝试过try 。 我注意到这样做的人已经做出了积极的报告。

PS: try问题再次开放以供反馈。 我们在那边继续。

@cstockton哦,绝对的。 澄清一下,我要说明的是,在大多数情况下,调用json.Unmarshal类的函数而不捕获错误值是 _already_ 不好的做法,但通常不认为defer file.Close()不好做法defer func() { err = file.Close(); if err != nil { ... }; }() ,我们学会了很容易区分。 所以它应该(可能)与您提议的更改有关。 我最初想到有人在他们打算处理错误时天真地使用foo := strconv.ParseFloat(bar, 64)时感到畏缩,但经过简短的考虑,我认为这毕竟不是问题。

@sirkon关于https://github.com/golang/go/issues/32825#issuecomment -507167388 :请将这些明显不合格的陈述排除在讨论之外 - 它们在这里没有位置。 只是为了记录一下,Go 中的许多想法实际上来自 80 年代(包、单独编译等)而不是 60 年代,而且许多想法更年轻(goroutines、接口)。 这些想法可能看起来仍然陈旧,但它们经受住了时间的考验(至少 CS 出现的时间很短)。

@turtleDev关于https://github.com/golang/go/issues/32825#issuecomment -507231353:Go 做了异常处理,它是用panicdeferrecover - 我们只是不称其为“异常处理”,因为该术语带有对 Go 具有误导性的含义。 但要清楚的是,Go 可以做raisetry-catch可以做的所有事情,甚至更多(因为与try-catch ,可以使用defer有条件的)。 谢谢。

@sorenvonsarvort关于https://github.com/golang/go/issues/32825#issuecomment -507268293:如果您想对每种情况进行不同的错误修饰,请使用if语句。 请参阅设计文档。 这个问题现在已经回答了很多次了。 谢谢。

@cstockton关于https://github.com/golang/go/issues/32825#issuecomment -507306652:是的,我们已经考虑过这样的机制。 具体来说,我们已经考虑过“扭转局面”,而不是提供try ,只提供handle ,它声明了一个错误处理程序。 如果处理程序存在(并且仅在那时),则只需将err留在赋值的 LHS 中,并且将对其进行隐式检查(就像使用不可见的try )。 它确实看起来不错,但它也完全不可见,这是一个很大的危险信号。 我们确实希望异常处理在代码中的每个地方都明确可见。 没有它,几乎不可能阅读代码并查看错误检查发生的位置。

@griesemer感谢您的澄清。 但是panic 和recover 有不同的用例,并且在大多数情况下很难在任何生产代码库中找到。 这使您只能对流结构进行有限的控制。 添加一个新的结构会破坏这种一致性,因为您现在拥有一个新的流程结构控制,可以执行诸如返回之类的操作。

@matthew-noken 关于https://github.com/golang/go/issues/32825#issuecomment -507323973:您提出了一个有趣的想法; 它看起来非常像调试器的观察点机制。 有一些问题需要回答: on块是否必须返回(我怀疑是这样,否则你会进入意大利面条代码领域)? 可以有多个这样的on语句吗? on条件有多复杂(必须在每次分配时对其进行评估)? 请注意,我们不能有任意表达式,因为我们必须使用on语句唯一地标识变量。 此外,在 Go 中有点令人厌恶: on构造意味着要在其他地方执行的不可见代码。

如果你想更多地探索这个,我建议在别处讨论这个(golang-nuts,或者一个不同的新提案)。 谢谢。

@as关于https://github.com/golang/go/issues/32825#issuecomment -507345821:

指针算法被排除在 Go 之外,前提是它可以很容易地编写糟糕的、损坏的代码

实际上,它被排除在外是因为它会使垃圾收集变得困难或不可能(是的,人们也可以编写不安全的代码)。 但这里更重要的一点是,有具体的证据和经验支持这一决定。

目前还没有经验和证据表明嵌套的try将会流行或普遍。 但请参阅https://github.com/golang/go/issues/32825#issuecomment -507358397。

@turtleDev关于https://github.com/golang/go/issues/32825#issuecomment -507368167 : panic是 _exactly_ 一个例外,而延迟函数中的recover本质上是一个catch 。 在生产代码中可能更难找到它们,因为在 Go 中我们不建议您使用异常编写代码; 它们只应在特殊情况下使用。

关于控制流结构的数量:提案很明确, try只是语法糖,没有别的。

我试图在这个线程上回答这个线程中最近的一些评论。 但是让我们在实际的try问题 #32437 中继续对try提案进行新的评论(从今天起再次解锁); 并将此问题保留给leave err != nil alone讨论。 谢谢。

@cstockton关于https://github.com/golang/go/issues/32825#issuecomment -507306652 的另一条评论:如果我们实现了这一点,那么从

    func f() int { ... }
    ...
    x := f()

并更改为

    func f() (int, error) { ... }

这意味着x := f()的行为会突然而无声地变得非常不同。

我运行了一些类似于@lpar对我们所有 go 存储库所做的实验。 结果在这个要点中: https :

@ianlancetaylor我实际上觉得在大多数情况下这会很好地解决问题,并且引入更好的错误报告的影响要小得多。 考虑两种主要情况的完整示例,首先调用者返回错误:

func f() int { ... }
func a() error {
    x := f() // if f() is updated to return an error, we get automatic error propagation by default
    ...
}

func b() {
    x := f() // if f() is updated to return an error, we get the same compiler error 
    // assignment mismatch: 1 variable but pkg.f returns 2 values
}

我认为在一般情况下,这是这种方法的一个很好的好处,我认为这会产生问题的极端情况已经很脆弱了。 我能想到的唯一一个可能真正令人讨厌的是使互斥锁死锁:

func (t *T) a() error {
   t.mu.Lock()
   x := f() // if f() is updated to return an error, we get automatic error propagation by default
   if x > 15 {
     t.mu.Unlock()
     return errors.New("t > 15")
   }
   ...
}

但是我认为这样编写的代码如果依赖于外部库调用来成功获得有效的程序状态,那么它已经容易出现死锁。 如果它存储在一个可以超越恐慌的范围内,那么如果同一个库通过 NPE 引入运行时恐慌,它就会死锁。 此外,编写这样的代码的一个主要动机是延迟存在于堆上的历史成本。 随着单延迟在堆栈上的性能改进,这样的代码并不是真正必要的。 我认为这种类型错误的任何派生都很容易纠正。

最后,像用工具支持“尝试”缺点的论点也可以应用在这里。 鉴于越来越多地采用 go-modules,我们有一个很好的机会注入“库升级 linting”步骤,以将此类更改清晰地呈现在用户面前。

@griesemer

关于控制流结构的数量:提案很明确,try只是语法糖,没有别的。

请原谅我,但try不会是一个宏(如 C),所以实际上,对于最终用户来说,它只是流程语句的另一种控制。

我相信在这一点上我没有客观的论据,所以我承认我们可能需要更好的错误处理,但我觉得try可能不是最好的解决方案。

同样,这些是我的意见,我只是代表他们。 我相信围棋团队对此的考虑比我以往任何时候都多。

侧面:让我感到奇怪的是,这个问题有 1335 票赞成,而try提案(#32437)只有279 票反对。 我希望人们对此投票反对try提案,以便社区对此的感受更加明显,因为这两个提案是相互排斥的。

@griesemer

错误处理已经因情况而异:有时我们返回错误不变,有时我们返回修饰的错误,有时我们对错误采取行动,有时我们(可以正确地)忽略错误。

同意,这很明显。

他们唯一共享的东西(除非我们忽略错误)是err != nil测试(我们已经可以通过多种方式进行测试)。 尽管新语言特性能够捕获所有这些情况(或其中的 95%)会很好,但这样的构造很可能以非正交方式干扰我们已有的其他控制构造。 也就是说,我们所能希望的最好的事情就是让其中的一些案例(可能是其中的 20%,也许是 50%)变得更好。

提议的try()语句也以非正交的方式“干扰” ifreturn ,所以我会说这个论点是不正确的。 出于这个原因,这里有些人不喜欢try() ,但我不同意。 Go 不是 Oberon,它简单但不简约,Go 更实用。

我们不同意的地方是,甚至值得为一种语言结构而烦恼,正如您自己承认的那样,只有有限的用途和适用性,并且已经可以使用ifreturn 。 我认为很多人,像我一样,对这个帖子竖起大拇指,对try()感到不知所措,他们宁愿根本不拥有它。 即使它与 return 不正交,一个更强大、更通用的try()可能是大多数 Go 程序员希望看到的。

@beoran

您写道,您想要一个“更强大”、“更普遍有用”的try()

@griesemer提到了 4 种情况:

  1. 返回错误不变
  2. 返回装饰错误
  3. 对错误采取行动
  4. 忽略错误

try()通过设计解决了 1:它实际上是if err != nil { return ..., err }的快捷方式。

现有的语言结构解决了 3 和 4。我们已经可以用if err != nil { ... }错误,在这种情况下很难找到更简洁的结构。 我们已经可以忽略_的错误。

这给我们留下了 2(返回一个装饰错误)。 try()提议建议使用 defer 语句来修饰错误,或者如果每个错误必须以不同的方式修饰,则使用标准的if err != nil { ... }构造。

提案的这一部分很好地解释了推理:

我们对这个提案的第一次迭代受到了关键部分错误处理的两个想法的启发,即使用内置而不是运算符,以及一个普通的 Go 函数来处理错误而不是新的错误处理程序语言结构。 与那篇文章相比,我们的错误处理程序具有固定的函数签名func(error) error以简化问题。 错误处理程序将在出现错误时由try调用,就在从封闭函数返回的try之前。 下面是一个例子:

handler := func(err error) error {
        return fmt.Errorf("foo failed: %v", err)  // wrap error
}

f := try(os.Open(filename), handler)              // handler will be called in error case

虽然这种方法允许指定高效的用户定义的错误处理程序,但它也引发了许多没有明显正确答案的问题:如果提供了处理程序但为 nil 会发生什么? try恐慌还是将其视为不存在的错误处理程序? 如果处理程序以非 nil 错误调用,然后返回 nil 结果怎么办? 这是否意味着错误被“取消”了? 或者封闭函数应该返回一个 nil 错误? 也不清楚允许可选的错误处理程序是否会导致程序员完全忽略正确的错误处理。 也很容易在任何地方进行适当的错误处理,但会错过一次try 。 等等。

下一次迭代取消了提供用户定义的错误处理程序的能力,转而使用defer进行错误包装。 这似乎是一种更好的方法,因为它使错误处理程序在代码中更加明显。 这一步消除了有关作为错误处理程序的可选函数的所有问题,但要求如果需要访问它们,则需要命名错误结果(我们认为这没问题)。

[...]

如果我们确定具有某种形式的显式提供的错误处理函数或任何其他与此相关的附加参数是一个好主意,则可以将该附加参数传递给 try 调用。

你不同意这个推理吗?

我知道提案的这一部分并且我不同意它,因为由于传递错误处理程序的想法而提出问题的事实并不意味着我们不需要这样的功能。 这只是意味着我们必须好好思考以合理的方式回答这些问题。

此外,现在,我们使用if err != nil语句处理所有 4 个错误用例,这是被广泛理解的、一致的 Go 习惯用法。 仅对案例 1 使用try() ,可能对于案例 2,如果我们不介意在 defer 语句中进行错误包装的开销,则意味着错误处理的代码将被拆分为iftry不一致,如果错误处理发生变化,我们将不得不在一个和另一个之间切换。

一个更强大的try() ,可以在所有情况下使用,这将允许我们几乎总是使用try() ,然后这将成为 Go 的新的、一致的错误处理习惯用法。

然而,对于现在的try() ,它的适用范围还不够广泛,并且没有足够的便利来将上述错误处理中的不一致引入我们的代码库。 这就是为什么我对当前的try()提案感到不知所措,并认为什么都不做更好。

@beoran我认为情况 1 和 2 的共同点是从函数返回错误(分别没有和有装饰),而情况 3 和 4 没有(分别对错误采取行动并忽略错误)。 我认为 'try()' 的重点是案例 1 和 2。

如果try()提案可以通过接受一个可选的处理函数来改进以处理情况 12,那会怎么样? 当然,这需要对提案中列出的问题找到合理的答案,但这似乎是可以解决的。 你会支持这样的事情吗?

给我出来。 如果我们希望用户检查错误,我们可能应该添加检查错误(有点像检查异常)。 这样我们就尽可能明确,用户知道他们正在检查所有错误。

严肃地说,如果我是做出这些决定的幕后推手,我真的很想重新利用 Kotlin ?运算符或类似 rust unwrap()行为。

我认为其中任何一个都会有所改进:

getFile()?.getPath()?.toString()

如果在此过程中出现错误,您将获得nil返还,或者

get_file().unwrap().get_path().unwrap().lossy_to_string()

如果出现错误,它会在中途爆炸。 Rust 通过具有表现力的match函数处理第二个错误,该函数允许您对错误进行详尽的搜索并分别处理每个错误,所有这些都返回链中某种类型的值。

19 年 7 月 2 日,Nicolas Grilly [email protected]写道:

@beoran我认为案例 1 和案例 2 的共同点是从
功能(分别无装饰和有装饰),而案例 3 和 4
不要(分别对错误采取行动并忽略错误)。 我认为'尝试()`
重点是案例1和2。

没有看到更多关于我的建议的讨论,我有点失望
它是“错误返回”的“返回”部分需要
解决,而不是“错误”部分。

再说一次,也许我应该遵循更正式的方法。 我是
根本不擅长制定提案,我倾向于到处走
地方。

在我看来,情况 1 和 2 更适合使用“失败”命令
关键字清楚地表明(在一些习惯之后)
程序流程的变化,不受任何限制
不便的传统就其完整的语法而言。

然而,无论我们喜欢与否,由此得出的结论是
“err”作为最后一个返回参数的定位即将
成为法律而不是惯例。 那是许多“意外”或
需要考虑的“附带”后果。

还会有许多其他这样的后果,从最小到
灾难性的,有些只是机会主义地捎带在
提议。 谨慎起见,我个人会犯错,我确实
理解围棋团队和罗伯特特别寻求的原因
支持和毫无疑问受到伤害,阻力原来如此
伟大的。

这是政治意识形态的冲突,也许我们需要挖掘很多
更深入地寻找需要一些园艺关注的真正根源。

@lootch您是否特别担心错误需要成为try工作的最后一个返回参数? 这不是事实上的约定吗?

(顺便说一句,我们并没有因为阻力而“受伤”——主要是对巨大的反应感到震惊。)

@griesemer

我只想写一个简短的评论,说我写了很多 Go(自 2009 年首次公开发布以来,我一直在使用 Go——请参阅我的 github),我欢迎围绕错误处理改进人体工程学。 虽然 Go 的错误处理的明确性很好,但在顺序代码中也很痛苦。 根据我的经验,缺乏对实际值本身的内省和输入(这在不同的提案中得到解决)实际上并不能提高程序的弹性。

几年前我对此感到很恼火,我实际上写了一些关于恐慌和恢复的糖,以允许它们(主要是)像未经检查的异常一样工作: https :

人们热切地主张保留一种极其冗长但无益的传播错误条件的方式,这似乎很疯狂。 我在我的代码(和其他人的代码)中编写并发现了真正的错误,人们在这些错误中弄乱了if err != nil条件并创建了错误。 这些错误真的没有理由被写入。 静态检查可以帮助但不能消除这些错误。

我支持围绕错误处理改进人体工程学的建议。

提案流程是为了,引用https://golang.org/s/proposal ,“提案变更到 Go”。
这个问题不是提议改变,所以它真的是一个类别错误。
如果有人提交了标题为“让选择单独进行”的提案,我们会将其作为非提案关闭。

但实际上,这个问题只是对#32437 等其他问题的讨论的延伸,所以我将把它留作讨论“什么都不做”的好地方。

我将其标记为 Proposal-Hold 以表明这里没有具体的决定,更多的是元决定。

侧面:让我感到奇怪的是,这个问题有 1335 票赞成,而try提案(#32437)只有 279 票反对。

当我意识到它时,尝试提案已被锁定,因此我无法对其进行投票。 我想这也是最初输入此提案的原因。

19 年 7 月 2 日,Robert Griesemer通知@github.com 写道:

@lootch您是否特别担心错误需要成为最后一个
try返回参数是否起作用? 这不是已经成为事实吗
习俗?

我的意思是,如果这成为“事实上的”一成不变,那么
那扇门应该打开,让“错误”成为一个可选的、特殊的
参数类型并接受它,因为对于每个正确的结果
很多时候是错误的,他们也可能是
以特殊的方式处理。

一旦认为需要调查“返回”语句的想法
更深入(并且 try 提议似乎暗示了这一点
方向),那么错误条件不再“仅仅是一个值,并且
Go 的方法开始类似于最好的纸牌屋
日期,不过是纸牌屋。

我不情愿地使用 Go 的一些更聪明的技巧(我有
在别处提到 x++ 和 x-- 没有特色 - 大多数情况下,我滑倒了
有时 - 在我的代码中)所以“尝试”对我来说仍然是其中之一,
但我原则上不反对它的引入,我只是觉得
说了这么多,但仍然没有所有可能的缺点
已经被揭露(我所看到的结果的意外后果
最终的政治决定)。 对此进行测试可能会很好
还是不好。

我看到的是恐慌/恢复受到了不好的说唱以及它的不公正
回来困扰我们。 同样,我还没有使用过,而且我
害怕我不得不面对的那一天,因为我不是罐子里最锋利的饼干
我会觉得这很难,但鼓励恐慌/恢复
对于适合它的罕见场合(它们都没有经过处理)
Rob Pike 在他关于错误刚刚返回的精彩演讲中
值,如果我没记错的话),所有这些都会更清楚
Go 已经可以处理错误以及不需要的预期
探索超出其可用选项的沼泽
边界。

也就是说,尝试进行测试是很有意义的,它是,在
所有,一个可选功能。 但一个副作用是这个
交换是关于:“事实上”的后果是什么
强制错误处理函数具有“错误”类型参数
在他们的返回参数列表的末尾? 这将如何改变
认为“错误只是价值观”? 它将如何与
现在更类似的“逗号确定”范式? 这还会是什么
新原理产生?

最重要的是,我认为由这个小山丘构成的山是
表明 Go 已经达到了改变人生的里程碑。 几乎
当然,2013 年的假设很可能不再成立
抓住。 这对任何人来说都不是新闻,但它表明 Go2 可能
向后兼容 Go1 并不像过去那样美妙
过于坚定地提出(在我看来)。

@lootch感谢您的详细回复。 以向后兼容的方式扩展现有语言确实非常困难,所以我同意你的看法,如果不需要向后兼容,也许人们可能会做不同的事情。 我们很快(一旦模块变得普遍)能够进行不需要严格向后兼容性的语言更改。

正如你所说, try是一种可选机制,而且 - 我认为值得重复 - 尽管它周围有很多喧嚣,但这是一个非常简单的机制,使用现有的 Go 功能很容易解释 - 只是我们可以'我们自己不要将trytry类的辅助函数用于错误处理是很常见的。 毕竟,这正是 Rob Pike 所说的错误是值的全部内容:它是带有错误的编程。

令我感到非常惊讶的是,关于添加一个每个人都可以随意忽略的非常小的辅助函数引起了如此大的骚动,但人们正在认真考虑重大的语言变化,例如on error确实强烈暗示了一种特定的风格语言中的错误处理。

再次感谢您的输入。 这一切都非常有趣。

我不确定这个提议是否适合讨论它,但鉴于在这个线程中已经对 try 关键字进行了生动的讨论,我现在只考虑它的主题:)

我想知道 Google 的人是否愿意在他们的内部 Golang 存储库中实现 try 关键字,然后将现有的 Google 代码库转换为使用该关键字。 通过仅将其保留在内部,它可以轻松地将其还原(即,负担由单个公司承担,而不是整个社区)。

这将允许在现实世界的高 sloc 代码库中对此功能进行一些案例研究。 Facebook 过去在 PHP 功能方面做过类似的事情,这样他们就能够在向整个社区提出建议之前对某些功能进行微调。

如果您要编写一个案例研究,说明 try 关键字在实践中的使用方式以及它在现实生活中增加的价值,这可能会提供一个令人信服的案例。 如果它(假设)不会提供任何现实世界的价值,那么了解它也很有价值。 有时有些东西在纸上看起来很好,但在实践中却行不通——反之亦然。

@Freeaqingme我们已经制作了一个暂定的 CL,展示了try在标准库中的样子: https :

还鼓励您在代码库中使用tryhard并报告。

谢谢。

@griesemer我可能没有说清楚。 我假设谷歌在内部使用他们自己的 Go 构建。 我的建议是将您上面链接的补丁应用到内部 Google 构建,然后转换(部分)Google 代码库 - 不仅仅是 stdlib,尤其是用 Go 编写的内部项目。 如果谷歌工程师在现实生活中使用它几个月,这将提供关于它在实践中如何工作的很好的见解。

显然我也可以自己应用它并在我自己的代码库中使用它(我也可以)。 但我只是一个代码库相对较小的单一开发人员,所以如果很多 Google 员工都使用它,这将不具有代表性。

就我个人而言,我仍然对这个功能持观望态度。 一方面,我很欣赏这样一个事实,它每次可以为我节省几次击键次数。 但有时我可能很懒惰(毕竟我是人类),并且尽可能多地嵌套 try 语句。 如果此功能存在,现在我将更加自律。 但即使我是,外部库仍有可能过度使用/滥用此关键字,而我的代码库却受到影响(就这些库的可调试性、可扩展性而言)。

@Freeaqingme 明白了。 我们当然可以在内部存储库上运行try 。 我不确定我们是否可以转换和转换回来 - 这里涉及大量成本。 此外,由于我们无法报告内部详细信息,因此我们只能报告此实验的汇总(统计数据)。 也就是说,社区没有简单的方法来验证我们的声明。 最后,Google 的代码库可能不具有代表性。

但是,谢谢,点了。

@griesemer我很欣赏这可能是一项代价高昂的努力。 在这种情况下,我不会这样做。 如果这只是应用您的尝试性项目的问题,那么成本可能会受到限制。 但这确实是由 Google 员工(我不是)来确定的。

我了解您无法报告单个 Google 项目或内部基础架构。 然而,也许可以分享一些轶事。 但我个人认为更有价值的是一些谷歌员工报告它是如何为他们工作的。 无需详细说明,诸如“我期望 X 但当我遇到 A 和 BI 之类的情况发现 Y”之类的陈述时,我会发现非常有价值。 我发现验证这些报告的需求为零。

最后,Google 的代码库可能不具有代表性。

可能是,也可能不是。 但是有很多人在谷歌工作,所以我认为大部分代码库不是基于一个人如何决定编写它,而是基于许多不同贡献者(员工)的贡献。 在这方面,我希望事情尽可能具有代表性。 可能没有像 100% 具有代表性的代码库这样的东西。

保持原样,直到我们找到更好的解决方案, try不是我们要找的那个。 无需对此采取偏见行动,慢慢来吧作者。 我坚信,就我每天与全球地鼠人交谈而言,Go 社区的大多数人不接受try提案 atm。

引入新语法意味着每个人都必须升级他们的 Go 版本。 我仍在使用 Go 1.10,因为我的工作流程基于这样一个事实:我可以go get东西,然后从命令行使用它们(我的GOPATH在我的PATH ) . 我最近在尝试go get使用模块的其他人的库时遇到了问题。 我收到.../v2不可用的错误。 这意味着代码中已经存在分裂(想想 Python 2 和 3)。 对我来说,Go 1.11 之前和 Go 1.11 之后都有一个世界。 这是非常烦人的,并且为在 Go 中工作以及错误处理的东西引入新语法根本不是一个好的权衡。 它引入了更多的碎片化。

19 年 7 月 4 日,gonutz [email protected]写道:

引入新语法意味着每个人都必须升级他们的 Go
版本。 我仍在使用 Go 1.10 因为我的工作流程基于事实
我可以go get东西,然后从命令行使用它们(我的
GOPATH在我的PATH )。 我最近在尝试go get时遇到了问题
使用模块的其他人的库。 我收到一个错误, .../v2
无法使用。 这意味着代码中已经存在分裂(想想
Python 2 和 3)。 对我来说,Go 1.11 之前和 Go 1.11 之后都有一个世界。
这很烦人,并为有效的东西引入了新的语法
以及 Go 中的错误处理根本不是一个好的权衡。 它
引入了更多的碎片化。

如果有任何安慰的话,我在面对面时处于完全相同的位置
去模块。 我还没有找到时间和机会去熟悉
和他们一起,所以我也坚持使用 Go1.10。 也许应该是
一个值得拥有的调查。

卢西奥。

——
你收到这个是因为你被提到了。
直接回复此邮件或在 GitHub 上查看:
https://github.com/golang/go/issues/32825#issuecomment -508372318

——
卢西奥·德雷
2 Piet Retief St
凯斯特尔(东部自由州)
9860 南非

电话:+27 58 653 1433
手机:+27 83 251 5824
传真:+27 58 653 1435

我是一个新的 golang 开发人员(仍在学习 go)。 我认为当前的错误处理很好,因为它使我们可以轻松地管理错误。 作为 android 开发者,我认为try-catch比 golang 中的if err != nil{ }更难管理我们的错误。 而且我认为显式错误处理总是比隐式错误处理更好。

附注。 对不起我的语言。

leave it alone

它没有坏....

喜欢模因, @Daniel1984 :-)

顺便说一句, try提案确实没有留下if err != nil ; 它只是为您提供了一个有意义的附加选项。

我认为不应该包括try 。 关于包含try

  • 程序员减少了他们的击键次数。
  • 程序员可以有一个从当前函数 à la 宏返回的简写。
  • 这不是必需的。
  • 它会被普遍使用。
  • 很明显魔术发生在哪里(不像Java的throws )。
  • 看到nil支票的海洋时,眼睛不再呆滞。
  • 最适合简单的实现。

骗局

  • try为现有操作添加重复方法。
  • 对于try从当前函数返回是出乎意料的,AKA 更神奇。
  • 它增加了错误检查的不一致。
  • 没有 Go 经验的程序员不会理解。
  • 不改变错误处理。
  • 不太清楚(函数返回值和表达式值不匹配)。
  • 甚至很难用语言来描述try发生的事情。

@griesemer这正是我不喜欢的。 事情应该很简单,我不想为了有两种方法来实现同样的事情而增加语言的复杂性。 有一些模式可以避免这种if err != nil冗长。 https://www.ardanlabs.com/blog/2019/07/an-open-letter-to-the-go-team-about-try.html

Go2 提案 #32437 为语言添加了新语法,使if err != nil { return ... }样板文件不那么麻烦。

有多种替代方案:#32804 和#32811 作为原始方案并不普遍。

在混合中加入另一种选择:为什么不保持原样

我开始喜欢if err != nil构造的显式性质,因此我不明白为什么我们需要新的语法。 真的有那么糟糕吗?

非常这个。 显式代码是正确的代码。 我在异常处理程序中看到的恐怖足以永远摆脱那个可怕的不可读的构造。

似乎有一个巨大的滞后,人们开始只发表评论
不由得给人一种新鲜的感觉
给他们的消息。

这也需要考虑。 小道消息显然不像
正如人们所想的那样迅速。

卢西奥。

并更改 gofmt 以允许单行 if 语句,简单的手球和错误直到
调用函数。 这将消除很多混乱。

代替:

错误 = myFunction()
如果错误!= nil {
返回错误
}

允许:

错误 = myFunction()
如果错误!= nil {返回错误}

顺便提一下,如果 err != nil 的话,try 提议确实会离开; 它只是为您提供了一个有意义的附加选项。

这个确切的理由是 Go 成为另一个 C++、C#、Scala、Kotlin 等的原因。“好吧,如果你不想,你不需要使用它”是功能臃肿的语言是如何产生的。

编辑 - 这可能遇到了错误的方式。 我并不是说try会将 Go 变成一种功能臃肿的语言。 我是说这里的理由有点缺陷

@deanveloper您清楚地举例说明了“try”难以理解的错误行为:
https://github.com/golang/go/issues/32437#issuecomment -498932961

即使有点重复,我也同意OP。
此外,imo 提出的所有替代方案都引入了无用的复杂性。

当范围内只有一行时,省略大括号会很酷

@发疯

这意味着代码中已经存在分裂(想想 Python 2 和 3)。 对我来说,Go 1.11 之前和 Go 1.11 之后都有一个世界。

我是一个长期的 Python 程序员,与从 Python 2 迁移到 Python 3 的灾难相比,你提到的关于 Go 模块的所谓“分裂”根本算不了什么。

这是非常烦人的,并且为在 Go 中工作以及错误处理的东西引入新语法根本不是一个好的权衡。 它引入了更多的碎片化。

try是提案中的内置函数。 它完全向后兼容。 如果您的代码已经使用try作为标识符,那么您的标识符将隐藏try内置。

@pongsatornw

我认为当前的错误处理很好,因为它使我们可以轻松地管理错误。 作为一名 android 开发者,我认为 try-catch 比 golang 中的 if err != nil{ } 更难管理我们的错误。 而且我认为显式错误处理总是比隐式错误处理更好。

您是否完整阅读了提案try只是一个内置函数,有助于分解if err != nil { return ..., err }重复。 Go 中错误处理的一般逻辑保持不变。 它仍然是明确的,错误仍然是值,并且没有 try-catch(也称为异常)。

@kroppt

  • try为现有操作添加重复方法。

try只是分解重复代码。 这与append 。 我们可以在每次将元素附加到切片时自己编写它,但调用append更容易。

  • 它增加了错误检查的不一致。

您可以使用[...:...] “手动”操作切片,也可以使用append ,具体取决于您的操作。 没有不一致之处。 它们只是用于不同任务的不同工具。 错误也一样,使用普通的if err != nil { ... }try ,具体取决于手头的任务。

  • 对于try从当前函数返回是出乎意料的,AKA 更神奇。

这是出乎意料的,因为它是新的。 随着我们越来越多地使用它,我们已经习惯了。 我不认为这是魔术; try 的规范非常简单。

  • 没有 Go 经验的程序员不会理解。
  • 甚至很难用语言来描述try发生的事情。

没有围棋经验的程序员不会理解chan , defer , 'go , iota , panic , recover , <- , type assertions, and many other things either without reading the documentation. try` 与大多数情况相比很容易。

  • 不改变错误处理。

也许这是一件好事,据地鼠要求不理会if err != nil ;-)

@marcopeereboom

显式代码是正确的代码。 我在异常处理程序中看到的恐怖足以永远摆脱那个可怕的不可读的构造。

try与异常处理完全无关。 你读过完整的提案吗? 例如,没有什么可以与 Java 或 Python 中的异常处理相媲美。 try是明确的。 错误必须在函数签名中提及。 必须在调用站点处理错误。 没有堆栈展开。 等等。

@大风93

当范围内只有一行时,省略大括号会很酷

我想大多数地鼠都有同样的想法,我在问题跟踪器中多次阅读了类似的建议。 但它的变化比try大得多。 仅针对if语句执行此操作是不合理的。 因此,您必须在接受块的任何地方更改此设置。 如果没有{标记块的开始,您必须指定一种方法来分隔条件表达式的结束。 您必须更新语法、解析器、gofmt 等。这将彻底改变语言表面。

@ngrilly
适度和保持语言简单很重要。

您使用的一些论点可以证明对规范进行大量更改是合理的。 这里不仅有积极的地方。

我正在根据它是否会帮助或伤害来评估决定,不一定完全制定某种哲学。 您是正确的,规范中的某些内容违反了go建立的某些原则,但这都是关于适度的。 这种变化对我没有足够的积极影响来容忍负面影响。

@kroppt

保持语言简单很重要

我同意,我认为我们都为此而努力。

我正在根据它是否会帮助或伤害来评估决定,不一定完全制定某种哲学。

我认为我们都在根据其收益和成本评估try 。 讨论是关于定义和找到关于这些收益和成本的基于事实的共识,这就是我在之前的评论中尝试做的。

您是正确的,规范中的某些内容违反了 go 所依据的某些原则

在过去的几年里,我几乎阅读了 Go 团队发布的关于 Go 及其设计的所有内容,但我不明白你的意思。 我不认为try提案违反了 Go 的基本原则。

@ngrilly
https://talks.golang.org/2012/splash.article描述了go不同背后的一些概念 - 清晰和简单等。 我认为这就是我们中的一些人看到的与这种新变化的冲突。 它更简单,但不太清楚。 对我来说,简单性的收益似乎小于清晰度的损失。 也许我错了,只是我很谨慎。

@kroppt我已经阅读了这篇文章几十次 ;-) 我不同意 try 混淆代码的想法。 try 只是一个用于分解一些重复代码的内置函数。 作为程序员,我们一直都在这样做。 当我们识别出重复模式时,我们会将其分解为一个新函数。 如果我们不这样做,我们就会有一个很长的 main() 函数,其中内联了我们所有的代码。

@ngrilly
您所描述的内容在我的“专业”部分:

  • 程序员减少了他们的击键次数。
  • 程序员可以有一个从当前函数 à la 宏返回的简写。
  • 在查看nil支票的海洋时,眼睛不再呆滞。

同样,当我们在这里没有普遍应用某个原则时,我不认为提及该原则的普遍应用有什么意义。

我不同意尝试混淆代码的想法

更改的重点是混淆/隐藏/简化/表示代码 - 否则我们将看到原始错误检查块。 问题是它是否使含义不太清楚。

我认为go最初在简单性方面取得了很好的平衡,以至于它有助于而不是剥夺了清晰度。 我无法解释他们是如何做到的,但在我看来try没有。

我认为我们不应该将冗长视为一个问题。 代码需要被人类阅读和理解——他们的时间比计算机更昂贵——而“理解”往往是困难和耗时的部分。

我发现 go 错误处理的缩进结构可以帮助我了解正在发生的事情。 每个错误检查都是明确的。 大多数未处理的错误也是显式的。 对我来说,这使代码易于理解。

我还认为虽然if err != nil检查看起来很乏味,但我实际上不需要_type_它们。 我只是让我的编辑来做。

@kroppt

更改的重点是混淆/隐藏/简化/表示代码 - 否则我们将看到原始错误检查块。

但是您可以将此参数用于任何函数调用! 如果我调用strings.HasPrefix("foobar", "foo") ,它会混淆代码吗? 你更喜欢写作和阅读l := len("foo"); len("foobar") >= l && s[0:l] == "foo"吗?

@rossmcf

每个错误检查都是明确的。

try 仍在明确检查错误。 这是尝试的存在理由。

我还认为,虽然 if err != nil 检查看起来很乏味,但我实际上不需要输入它们。

打字并不乏味。 当我们到处都有相同的 3 行时,阅读起来很乏味。 这是一种重复,我们作为程序员通常倾向于将这些因素排除在外。 也许try有其他缺点,但我认为不是这个。

尝试仍在明确检查错误
try抽象和strings.HasPrefix 之间的区别在于try隐式返回。
在阅读 go 代码时,我知道执行流程保留在我的函数中,直到我:

  • 读取没有返回类型的函数的右括号
  • 阅读关键字return , panic
  • 阅读syscall.Exit(code)
    使用try ,我无法以同样的方式阅读代码。 目视扫描行并看到零个“返回”语句不再意味着“这些行全部执行,或一个块,或程序终止”。

@ngrilly你可以在一个帖子中回复一个以上的人,仅供参考,几个小时内回复 10 条,一次连续回复 5 条,这让你很难跟上讨论。 除了一些谬论之外,在阅读了您的帖子后,我还没有看到任何新的具体论据形成描述尝试的好处。 我看到了一个好处:防止输入if err != nil 。 这是以引入新的方式来泄漏资源、编写不那么简洁的代码的能力以及最糟糕的是允许尝试嵌套的代价。

我觉得支持者形成的规范和论点具有误导性,它目前只提供了最好的例子,没有显示任何最坏的例子。 它没有评估或提及上述负面缺点或任何潜在的缓解因素。 它不能证明为什么它不将 try 的实现限制为建议和演示的用法。 工具tryhard用于显示更紧凑的代码,主观上为某些人提供更好的美感,而没有trytoohard工具显示它可以做什么的全部功能,例如深度嵌套的尝试声明。 最后,该名称本身在编程世界中无处不在地与异常相关联,允许它嵌套并与错误相关,并将其作为内置内容放置在不相关的恢复和恐慌中,从而使事情看起来不合适。 我相信并相信 Go 作者有能力想出更好的东西。

这里有太多的成本来证明我在支持者的回复中反刍的单个附加值是合理的:“我不再需要输入if err != nil ” - 与错误一起被强烈捍卫和根深蒂固的东西是整个 Go 社区的价值观。 我们已经使用if err != nil编写了长达十年的代码——其中一些最显着的技术进步(docker、k8s 等)同时使用并取得了巨大成功。

最后, if err != nil并不是隐藏在内置函数中的负担,而是我们都应该将其视为语言成功的核心要素。 即使我们集体接受它是一种负担,消除它的门槛也应该很高而且毫不妥协。 现在,尝试的很多方面都是妥协。

我对哪种方法更容易有意见,但它们是意见。 鉴于尝试很简单,当前的显式检查很简单。 伟大的两种方式都很简单。 对我来说,问题在于它增加了任何给定代码的读者和作者的认知负担。 现在,两者都必须解释多种做事方式。 并且作者必须选择以哪种方式做事和/或冒险以与他们正在处理的包或项目的其余部分不同的方式做事。 如果尝试替换显式检查,仍然会增加认知负荷,因为隐式返回是另一件要解析的事情。

_把所有这些放在一边,考虑到我们现在有两种同样简单的方法来处理错误,我们仍然有一个问题:_简单不再容易。 这为所有旨在避免的事情打开了大门。

添加这样的东西的门槛应该高得多,应该进行长时间的实验,以证明使用现场数据更好。

@cstockton

您可以在一个帖子中回复一个以上的人,仅供参考,几个小时内回复 10 条,一次连续回复 5 条,这使得讨论变得困难。

出于这个原因,伊恩7 天前

@therealplato

try 抽象和 strings.HasPrefix 之间的区别在于 try 隐式返回。

确实如此。 在读取函数并寻找“退出”点时,我们必须寻找 return、panic、log.Panic、os.Exit、log.Fatal 等……然后尝试。 有这样的问题吗? 函数中退出点的数量将保持不变,并且仍然会被明确标记,无论是否尝试。

阅读关键词返回,恐慌

恐慌不是关键词; 它是一个内置函数。 如果我们要批评 Go 团队的提议,他们在语言设计方面可能比我们任何人都更有能力,那么至少我们应该帮助他们正确定义事物。 😉

在读取函数并寻找“退出”点时,我们必须寻找 return、panic、log.Panic、os.Exit、log.Fatal 等……然后尝试。 有这样的问题吗?

这是一个问题,因为try可以出现在表达式可以出现的任何地方。 Go 中的每一个退出点都可以_only_作为单个语句出现, try是唯一可以作为表达式出现的东西。

@ngrilly

出于这个原因,伊恩在 7 天前建议将这个讨论转移到 golang-nuts(无法回复特定评论,没有线程),该建议被驳回以确保讨论是“官方的”。 我们有我们所要求的。

开始消息

@用户1
回复 1

@用户2
回复 2

结束消息

这就是本意。

@cstockton

我看到了一个好处:防止输入 if err != nil。

try防止重复输入读取if err != nil { return ..., err } (格式化为 3 行),而不仅仅是if err != nil

这是以引入新的方式来泄漏资源、编写不那么简洁的代码的能力以及最糟糕的是允许尝试嵌套的代价。

您提到的资源泄漏风险可以通过vetlint来防止。

关于“不太简洁的代码”, try的全部意义在于编写更简洁的代码,所以我不明白你的论点。

过度函数调用嵌套的风险并非特定于try 。 任何函数调用都可以过度嵌套。 代码审查和 linting 会一如既往地提供帮助。

我觉得支持者形成的规范和论点具有误导性,它目前只提供了最好的例子,没有显示任何最坏的例子。

也许这种感觉是相互的。 我们都是可爱的地鼠; 让我们不要陷入价值判断;-)

我看到支持者在回复中反驳:“如果 err != nil,我不再需要输入”

同样,我不再需要输入l := len("foo"); len("foobar") >= l && s[0:l] == "foo"
我可以用strings.HasPrefix("foobar", "foo")代替。
它与try有何不同?
我之前读过你会接受一个“受限”的try ,它会被命名为check并且会禁止嵌套。

我们已经使用 if err != nil 编写了长达十年的代码——其中一些最显着的技术进步(docker、k8s 等)同时使用并取得了巨大成功。

我们也有很多用 C、C++、Java 等编写的很棒的代码。按照这种推理,我们不会有 Go。

在阅读有关 Go 中错误处理的讨论时,我不觉得每个人都对try提案达成共识,因此我决定写一篇博客文章来演示try如何实现的使用: https :

Reddit 相关讨论: https :

我知道这个问题是针对try ,但我希望我的帖子可以带来一些新的观点:)

Go 被困在神奇的合乎逻辑的简单想法之间挣扎。

优点:

  • 减少样板
  • 简单的
  • 其他语言中的现有模式
  • 可选的

缺点:

  • 学习曲线
  • 基础魔法
  • 新类型的错误
  • Go 是固执己见且实用if != nil但您可以使用try

我觉得这个社区尤其如此。 这里与 Go Survey [1] 中投票的人不同。
选民可能不会将此作为主要关注点,而是留待未来考虑。
但由于它的位置,它被认为有影响。

IMO,添加语言功能是旧的,现代编程方式是在编辑器上添加更多功能,即 Emmet 或语言片段、代码折叠和着色、重构和格式化、调试和测试、建议解决错误并从 godoc 或堆栈溢出中引用, UI 在源代码之上,并保留源代码冗长
代码将if err != nil折叠成try

@ngrilly

try 防止重复输入和读取 if err != nil { return ..., err } (格式化为 3 行),而不仅仅是 if err != nil。

你相信我说“如果 err != nil 会阻止你输入”意味着我完全忘记了我们也阅读了我们输入的代码?

您提到的资源泄漏风险可以通过 vet 和 lint 来预防。

我链接了一个讨论,为什么我觉得 vet 和 lint 在这里不是一个合理的选择。

关于“不太简洁的代码”,尝试的全部目的是编写更简洁的代码,所以我不明白你的论点。

是的,您是否阅读过“编写不那么简洁的代码的能力”实际上指向的链接,您可能已经理解了我的论点。 _注意,花一点时间来理解与您一起讨论的人的论点是提供能够使他们承认您的观点的信息的第一步。_

函数调用嵌套过多的风险不具体尝试。 任何函数调用都可以过度嵌套。 代码审查和 linting 会一如既往地提供帮助。

有趣的是,让我们实际分解一下:

1)函数调用嵌套过多的风险不具体去尝试。

是的,这里的每个人都了解函数的工作原理。

2) 任何函数调用都可以过度嵌套。

是的,这里的每个人都了解函数的工作原理。

3) 代码审查和 linting 将一如既往地有所帮助。

是的,您已经提出了 lint 论点,而“代码审查论点”是我链接到您的帖子中提出的另一个外部语言控制。

也许这种感觉是相互的。 我们都是可爱的地鼠; 让我们不要陷入价值判断;-)

我不明白? 该提案没有关于实现提供的完整功能的示例,只有预期用途。 用于帮助衡量提案影响的tryhard工具以最有限和最合理的形式使用try ,这是一个简单的事实。

同样,我不再需要输入 l := len("foo"); len("foobar") >= l && s[0:l] == "foo"。
我可以使用 strings.HasPrefix("foobar", "foo") 代替。
与 try 有何不同?

我尽力从每个相反的观点中提取一个立场,否则我无法形成一个论据来拆除它。 我真的看不到这里,对不起。 我将用我能理解它的唯一方式来解释它:字面意思。

它( strings.HasPrefix )与try有何不同?

字符串.HasPrefix

func 有前缀

func HasPrefix(s, prefix string) bool

HasPrefix 测试字符串 s 是否以前缀开头。

尝试

func try是一个新的类似函数的内置函数,名为 try with signature (pseudo-code)

func try(expr) (T1, T2, … Tn)

其中 expr 代表传入参数表达式(通常是函数调用),产生 n+1 个 T1、T2、... Tn 类型的结果值,以及最后一个值的错误。 如果 expr 计算为单个值(n 为 0),则该值必须是错误类型并且 try 不会返回结果。 使用不产生类型 error 的最后一个值的表达式调用 try 会导致编译时错误。

try 内置函数只能在至少有一个结果参数的函数中使用,其中最后一个结果是错误类型。 在不同的上下文中调用 try 会导致编译时错误。

使用函数调用 f() 调用 try,如(伪代码)

x1, x2, … xn = try(f())

turns into the following (in-lined) code:

t1, … tn, te := f()  // t1, … tn, te are local (invisible) temporaries
if te != nil {
        err = te     // assign te to the error result parameter
        return       // return from enclosing function
}
x1, … xn = t1, … tn  // assignment only if there was no error

换句话说,如果 "expr" 产生的最后一个值是 nil,类型为 error,则 try 只返回前 n 个值,并去除最后的 nil 错误。 如果 "expr" 产生的最后一个值不是 nil,则封闭函数的错误结果变量(在上面的伪代码中称为 err,但它可能有任何其他名称或未命名)被设置为该非 nil 错误值,并且封闭函数返回。 如果封闭函数声明了其他命名的结果参数,这些结果参数将保留它们所具有的任何值。 如果函数声明了其他未命名的结果参数,则它们假定它们对应的零值(这与保留它们已有的值相同)。

如果 try 碰巧用在多重赋值中,如图所示,并且检测到非 nil 错误,则不会执行赋值(对用户定义的变量),并且不会执行左侧的任何变量分配被更改。 也就是说,try 的行为类似于函数调用:只有当 try 返回到实际调用站点(而不是从封闭函数返回)时,它的结果才可用。 因此,如果左侧的变量被命名为结果参数,则使用 try 将导致与今天发现的典型代码不同的结果。 例如,如果 a、b 和 err 都是封闭函数的命名结果参数,则此代码

a, b, err = f()
if err != nil {
        return
}

将始终设置 a、b 和 err,与 f() 是否返回错误无关。 相比之下

a, b = try(f())

如果出现错误,将保持 a 和 b 不变。 虽然这是一个细微的区别,但我们认为这样的情况很少见。 如果预期当前行为,请保留 if 语句。

它们的不同之处在于,在 try 的描述中找到的整个文本不存在于 strings.HasPrefix 中。 一个更好的问题是如何相似,我会回答他们都共享Calls 的某些方面,而没有其他方面。

我之前读过您会接受名为 check 并禁止嵌套的“受限”尝试。

很高兴您阅读了我反对 try 的中心论点:实施不够严格。 我相信要么实现应该匹配所有简洁易读的提案用法示例。

_或者_提案应该包含与实现匹配的示例,以便所有考虑它的人都可以接触到 Go 代码中不可避免地出现的内容。 以及我们在对非理想编写的软件进行故障排除时可能面临的所有极端情况,这些情况发生在任何语言/环境中。 它应该回答诸如具有多个嵌套级别的堆栈跟踪是什么样子的问题,错误的位置是否容易识别? 方法值、匿名函数字面量呢? 如果包含对 fn() 调用的行失败,下面会产生什么类型的堆栈跟踪?

fn := func(n int) (int, error) { ... }
return try(func() (int, error) { 
    mu.Lock()
    defer mu.Unlock()
    return try(try(fn(111111)) + try(fn(101010)) + try(func() (int, error) {
       // yea...
    })(2))
}(try(fn(1)))

我很清楚会有很多合理的代码编写,但我们现在提供了一个以前从未存在过的工具:在没有明确控制流的情况下可能编写代码的能力。 所以我想证明为什么我们甚至允许它摆在首位,我从不希望我的时间浪费在调试这种代码上。 因为我知道我会这样做,经验告诉我,如果您允许,就会有人这样做。 那个人经常是一个不知情的我。

Go 为其他开发人员和我提供了尽可能少的方式,通过限制我们使用相同的普通结构来浪费彼此的时间。 如果没有压倒性的好处,我不想失去它。 我不相信“因为 try 是作为一个函数实现的”是一个压倒性的好处。 你能提供一个原因吗?

不要在这个错误处理问题上浪费时间,给我们泛型,我们将创建类似 Rust's Result 的东西。

Go 被困在神奇的合乎逻辑的简单想法之间挣扎。

优点:

  • 减少样板
  • 简单的
  • 其他语言中的现有模式
  • 可选的

缺点:

  • 学习曲线
  • 基础魔法
  • 新类型的错误
  • Go 是固执己见且实用if != nil但您可以使用try

我觉得这个社区尤其如此。 这里与 Go Survey [1] 中投票的人不同。
选民可能不会将此作为主要关注点,而是留待未来考虑。
但由于它的位置,它被认为有影响。

IMO,添加语言功能是旧的,现代编程方式是在编辑器上添加更多功能,即 Emmet 或语言片段、代码折叠和着色、重构和格式化、调试和测试、建议解决错误并从 godoc 或堆栈溢出中引用, UI 在源代码之上,并保留源代码冗长
代码将if err != nil折叠成try

我是投票支持更严格的错误处理而不会忘记处理错误的人。 不是为了尝试。

我们需要的不仅仅是泛型来远程改造任何像 Rust 的Result类型的东西。 即使 _if_ Result类型可以单独使用泛型,初学者程序员也需要了解泛型才能正确处理错误,或者从函数“ Result方式返回错误” ”

@deanveloper ,我的观点是:与“语法更改”相比,我可以从泛型中受益更多,我相信这对社区来说也是如此。

@txgruppi我同意泛型应该具有更高的优先级。 我只是想说我不认为泛型将成为错误处理的良好替代品。

@deanveloper ,在我看来,这个错误处理问题只是表面问题,人们花时间讨论某些东西是否稳定并且运行良好,只是因为您必须输入一些额外的代码。 只需学习如何编写更好的代码并通过设计更改来解决这个问题。
在有人说泛型与使用更好的代码修复一样简单之前:编译时错误......

可以用代码片段或键盘宏解决吗? 那么这不是问题。

@txgruppi

只需学习如何编写更好的代码并通过设计更改来解决这个问题。

正如 Robert Griesemer使用他的tryhard工具发现的那样,标准库中 70% 的错误处理代码目前适用于try 。 更多的将符合更改代码的条件,例如使用(尚不存在的) fmt.HandleErrorf函数。 我希望你不要把标准库称为糟糕的代码。

可以用代码片段或键盘宏解决吗? 那么这不是问题。

这也是关于阅读代码。 这就是为什么我们不喜欢thing.Thing thing = new thing.Thing(thing.THING);

@faiface ,'if err != nil' 是否正在开发高质量软件? 我不这么认为。
缺乏泛型是否会妨碍开发优质软件? 是的。

我的看法是:我没有足够的知识来实现​​泛型,所以我需要有人来实现它,但是对于那些可以使泛型成为现实的人来说,这种错误处理只是浪费时间。 我不反对这种错误处理,因为它是一件坏事,我反对它是因为有更重要的事情需要解决。

@faiface标准库并不能很好地代表真正的 Go 代码。 这是因为标准库更有可能在不添加上下文的情况下简单地传递错误,例如io/ioutil从来不需要修饰错误,它可以简单地传递发生在io的错误

@deanveloper @faifaceGo Corpus对抗时:

--- stats ---
 401679 (100.0% of  401679) func declarations
  97496 ( 24.3% of  401679) func declarations returning an error
 991348 (100.0% of  991348) statements
 217490 ( 21.9% of  991348) if statements
  88891 ( 40.9% of  217490) if <err> != nil statements
    485 (  0.5% of   88891) <err> name is different from "err" (-l flag lists details)
  59500 ( 66.9% of   88891) return ..., <err> blocks in if <err> != nil statements
  29391 ( 33.1% of   88891) complex error handler in if <err> != nil statements; cannot use try (-l flag lists details)
    596 (  0.7% of   88891) non-empty else blocks in if <err> != nil statements; cannot use try (-l flag lists details)
  52810 ( 59.4% of   88891) try candidates (-l flag lists details)

因此,在实际代码中,40% 的 if 语句是为错误检查而编写的,而try可以立即消除其中的 59%。

我同意。 如果错误!= nil,我很好。 对于返回单个错误值的函数来说,它既简单又干净。 当错误的上下文很重要时,我也喜欢错误包及其原因/包装函数。 使用带有代码属性的自定义错误(据我所知)要求您要么进行类型断言,要么完全使用某些东西来代替标准错误接口。 无论哪种方式,我从未发现自己在阅读或编写 Go 代码时对当前的错误处理方式感到任何烦恼。 我遇到的烦恼是在处理一组项目时可能会发生多个错误的情况。 然而,这是设计问题而不是语言问题。

请注意,当我说“当错误的上下文很重要时”时,我指的是可能发生网络故障的情况,因此我想重试,或者“查找”类型调用的结果可能没有返回结果,因为有实际上没有,或者我抽搐的手指在我的 SQL 查询中添加了一个随机的 's',这让它爆炸了(这种情况经常发生在我身上......我可能应该检查一下是否有神经损伤)。

19 年 7 月 5 日,Nicolas Grilly [email protected]写道:

@kroppt我已经阅读了几十次 ;-) 我不同意 try 是
混淆代码。 try 只是一个内置函数,用于分解一些
重复代码。 作为程序员,我们一直都在这样做。 当我们确定一个
重复模式,我们将其分解为一个新函数。 如果我们没有,我们
将有一个很长的 main() 函数,其中内联了我们所有的代码。

我很想说你不诚实,但我尊重伊恩·兰斯·泰勒的
保持讨论礼貌的超人努力,我真的不能
看看任何人故意在这个论坛上撒谎会得到什么。

也就是说,“当我们识别出一个重复的模式时,我们会将其考虑在内
一个新函数。”当然,但不是通过提供一个相互冲突的结构
在 Go 开发的这个后期阶段,有两个
隐藏功能:首先是处理“返回”的函数
参数列表以error值结尾作为特殊的(或其他所有
作为语义错误),第二个它提供隐藏的控制流
与“返回”类似但不完全相同的绕道
陈述。

别介意使用“defer”来处理的火圈
使用更多神秘的“尝试 - 伪函数”。 有人
在其他地方说,大致“我不想在代码中遇到try
我读了”。我也有同感,那不应该被扫下
地毯。

我已经说过这是“错误返回”的“返回”方面
需要处理,而“on err”提议最接近
这个原则,但也有些曲解了规则。 我自己的也是
“失败”建议(它将最后一个参数移到第一个,即
让我不开心)。

更深入地说,除了 SNOBOL 之外,没有什么语言,
我熟悉的已经实现了 Rob Pike 所描述的飞跃
“错误就是价值”达到 Go 的程度,但有些东西丢失了
在此过程中:错误“条件”是“不是”值。 成功的
函数的完成是一种特殊情况,所以每种情况都是可能的
失败。

每个(适用于成功完成,其中可能有
不止一个)需要根据其优点来对待,但我们
坚持被调用的函数必须告诉我们它关于
以缩写形式完成的质量,具有
已经永远完成了,而 Rob 已经证明是一种误解。

为了说明这一点,请考虑 Reader 的返回值:
io.EOF 是一种特殊情况,有时成功有时失败
失败,但按照 Go 标准,这是一个错误(“io.Err != nil”)。
我们是否也有某种方式来缩写它? 几乎肯定
不是,因为我们已经习惯了“原谅”它的“错误”。

我一直在等待循环退出来传达类似的“状态”或
条件代码(搜索可能以找到的值或失败结束,如何
如果你想做不同的事情,你会告诉哪个? 你添加一个测试,
知识已经到位——同样的问题,不同的
语境)。

这些是对传统语言的真正改进:减少锅炉
相比之下,板块是荒谬的。

并且,与三元运算符的比较同样有效:如果 ?:
不允许“以免被滥用”,则不允许尝试,
或者,至少基于这些理由。

坦率地说,尝试是一个云雀。 它使 Go 对错误更有吸引力
观众。 除了这样做的危险 - 谁想要错误的人
加入我们的社区? - 有先例问题和问题
意料之外的后果。 我会说“废弃它”,让我们接受这一点
我们还没有达到可以向后兼容的程度
被藐视,所以错误处理必须保持冗长。

我们有恐慌/复苏,需要从三流提升
公民对愿意的帮助者它可以是所有的解释
这让新手(还有我,我承认我很害怕)更多
放心使用。

错误处理中的“延迟”构造(我采用了 -
没有意识到它在其他地方的进口 - 始终如一地完成
SQL 事务:tx.Rollback/tx.Commit) 对我来说是一个启示。
可能有更多的原则可以“在”范围内学习
Go 已经提供了什么:让我们暂时留在那个盒子里。

袖手旁观的这样一件事就是传递到错误报告
函数在常规下执行的“error.Methods”列表
条件(io.EOF,sql.ErrNoRows),而不是报告结果
作为黑白。 但我在这些事情上没有受过教育,我的建议
太天真了,让别人(罗杰,你在听吗?)培养出类似的
想法付诸实践。

卢西奥。

“打字并不乏味。阅读是乏味的,当我们有相同的
3行无处不在。 这是一个重复,我们作为程序员通常
倾向于将这些因素排除在外。 也许尝试还有其他缺点,但不是这个
一个我认为。”

再次,虚伪或至少,合理化。 我承认那些
三行被读取的次数比被写入的次数要多得多,但是
Griesemer 旨在减轻的痛苦在于写作而不在于阅读

  • 毫无疑问,他认为这是一个有益的结果。

但是“err != nil”是一个非常熟悉的标记,并且在“阅读”时 - 如
与使用编辑器“搜索”相反,这种模式既容易
现货且易于取消。 分解出来是不一样的
联赛。 而且价格不对。

“我们作为程序员”倾向于首先分解出更复杂的模式,
即使它们很少发生。 我们也知道“if err != nil { return
err }" 被编译成一个非常简单的指令序列,如果
没有的,让他们举手。 一个可以平等吗
相信它会发生在“尝试 - 功能”中吗?

卢西奥。

@lootch嘿伙计,称人们不诚实真的是徒劳无功的,我很确定他们不是因为您标记为这样的陈述非常合理。

作为程序员,我们确实排除了重复模式,这是绝对正确的。

很多重复的代码确实会减慢阅读速度,所以我也看不出这有什么不诚实的地方。

你对这些的反驳基本上是“来吧伙计,这没什么大不了的”。 嗯,对很多人来说确实如此。

谁希望错误的人加入我们的社区?

这是超级傲慢的守门人。 此外, tryhard工具显示try直接适用于当今的大部分 Go 代码库。 它可以直接应用于标准库中 70% 的错误处理代码。 通过更改代码(使用defer等进行错误修饰),我相信它将适用于所有 Go 代码中超过 80% 的错误处理。

同意,我在这里越界了。 我道歉。

我想我们中的一些人在这个讨论中变得很热
正在兜兜转转。

在19年7月7日,米哈尔什特尔巴[email protected]写道:

@lootch嘿,伙计,称人们不诚实真的是徒劳无功的,我是
很确定它们不是因为您标记为这样的语句很漂亮
合理的。

我们确实排除了程序员的重复模式,这绝对是
真的。

很多重复的代码确实会减慢阅读速度,所以我不明白这是怎么回事
也不诚实。

你对这些的反驳基本上是“来吧伙计,这不是这样的
大不了”。嗯,对很多人来说是。

谁希望错误的人加入我们的社区?

这是超级傲慢的守门人。 此外, tryhard工具显示
try直接适用于当今的大部分 Go 代码库。 它可以是
直接应用于标准库中70%的错误处理代码。 和
更改代码(使用错误修饰使用defer等),我相信
它将适用于所有 Go 代码中 80% 以上的错误处理。

——
你收到这个是因为你被提到了。
直接回复此邮件或在 GitHub 上查看:
https://github.com/golang/go/issues/32825#issuecomment -508971768

——
卢西奥·德雷
2 Piet Retief St
凯斯特尔(东部自由州)
9860 南非

电话:+27 58 653 1433
手机:+27 83 251 5824
传真:+27 58 653 1435

@lootch 支持自我意识! 我可以理解看到讨论陷入困境的沮丧。

我也有类似的看法,我在另一边。

或许双方只是不了解对方。 您是否阅读过我的博客文章“如何使用“尝试”? 我试图展示如何在实践中使用“尝试”,尽我所能保持公正?

在19年7月7日,米哈尔什特尔巴[email protected]写道:

[...]
或许双方只是不了解对方。 你
阅读我的博客文章如何使用'尝试'? 我试图
展示如何在实践中使用“尝试”,尽我所能留下
公正?

我承认我没有,我热切希望我永远不必:-)

你有没有考虑过我认为斯托克顿提出的方面,其中只有
尝试的好处显示出来,他要求柔软的下腹部
也被揭穿? 我担心我同意他的观点 - 无意冒犯 -
您的博客可能会遭受同样的缺点。

如果不是,那么请刺激我,好的阅读材料有一个特殊的地方
在我生命中 :-)

卢西奥。

@lootch我尽我所能在短代码中尽可能多地展示“尝试”的各个方面(即适用时和不适用时),并使其尽可能公正和现实。 但是,当然,不要相信我的话:)

这是对相关 Reddit 讨论的最高投票评论:

这是一个有用的无偏假设示例。 感谢您为对话添加了一些建设性的东西,而不仅仅是“它糟透了”。

@lootch我尽我所能在短代码中尽可能多地展示“尝试”的各个方面(即适用时和不适用时),并使其尽可能公正和现实。 但是,当然,不要相信我的话:)

这是对相关 Reddit 讨论的最高投票评论:

这是一个有用的无偏假设示例。 感谢您为对话添加了一些建设性的东西,而不仅仅是“它糟透了”。

以文件路径为参数的函数? 仅此一点就是此代码无法通过我的审查的原因。 如果缺少某些字段怎么办? 重新排序?

@sirkon为了不至于太长,当然简化了示例。 但是,修复您提出的问题所需的更改不会影响错误处理实践,这才是最重要的。

修复您提出的问题所需的更改不会影响错误处理实践

因为你这么说?

  1. 从你博客的标题开始:它应该被称为“如何不写”,因为,我再说一遍,使用文件路径作为参数是一种非常糟糕的做法,坦率地说,下面还有一整段代码。
  2. 你意识到这一点了吗
    go resp := Respondent{ Name: name, Gender: try(parseField(s, &line, "gender")), OS: try(parseField(s, &line, "os")), Lang: try(parseField(s, &line, "lang")), }
    会产生不良的错误信息吗? 至少应该有意外的字段错误消息和缺少字段的错误消息。 您的脚本的诊断不合格。

PS看看你的回购。 您是否意识到 Go 对您的任务来说是一个糟糕的工具? 你应该明白,在真正的 Go 应用实践中,最先看到日志的是运维工程师,而不是开发者。 正确的错误信息可以帮助他们自己解决问题。

@sirkon来吧,不要做

您是否意识到这会产生糟糕的错误信息?

它们完全适合模型。 该格式应按顺序包含所有字段。 错误消息非常清楚地告诉它。

如果您想质疑代码的质量,为什么不按照您的质量标准重写它呢? 如果您这样做,我会尽力重写您的代码以使用try

PS看看你的回购。 您是否意识到 Go 对您的任务来说是一个糟糕的工具?

你为我的任务推荐另一个吗? 我用了好几个。 顺便说一句,这是非常题外话。

@faiface

你为我的任务推荐另一个吗? 我用了好几个。 顺便说一句,这是非常题外话。

锈? C++?

@sirkon

锈? C++?

我们走了。 在使用 Go 之前,我已经使用过它们。 我从未回头。

@sirkon try一大缺陷是它不鼓励错误修饰。 在这种情况下,程序员展示了try可能应用,所以当然不会有太多的错误修饰。

此外,根据他们所从事的项目来诋毁人们完全是题外话,也是不必要的。 你对过去的一些评论非常粗鲁,我希望你至少意识到这一点。

@deanveloper感谢您的评论!

顺便提一句

在这种情况下,程序员正在展示 try 的可能应用程序,所以当然不会有太多的错误修饰。

如果您指的是我的博客,实际上有很多错误修饰正在进行,只是与@sirkon 的处理方式不完全相同。 以下是使用try的程序的一些错误消息:

parse respondnts.txt: open respondnts.txt: no such file or directory
parse respondents.txt: line 12: parse field gender: expected "gender:"
parse respondents.txt: line 9: expected empty line
parse respondents.txt: line 4: parse field lang: EOF

@faiface我的错误,我应该更具体。 try不鼓励在同一函数中包含多个错误消息时进行错误修饰。 可以使用check/handle草稿和“指定处理程序”反建议来做到这一点。 在指出的特定实例中(您在初始化结构时使用try时)能够在每条消息周围添加装饰会非常有用,但不幸的是,try 提议使这有点难以做到无需编写自己的函数。

不过,检查/处理在您的特定情况下不会有太大帮助。 但所提出的想法catch和其他反建议以try本来能够处理错误,如添加额外的装饰。

@deanveloper好吧,大多数情况下,您需要对函数中的所有错误使用相同的修饰,因为应该期望子函数提供自己的上下文。 但是,当您必须在单个函数中以不同的方式装饰事物时,仍然有一个简单的解决方案 try:

..., err := functionThatCanFail(...)
try(errors.Wrapf(err, ...))

或者简单地将大功能拆分为多个小功能。

那时我眼中的if err != nil ,但我想这是一个偏好问题。

然而,有时(如在结构初始化的情况下)拆分成多个函数并不是一个好主意。 虽然我猜我有点挑剔。

我实际上并不是超级反对try ,但我也不是完全的支持者。 我认为还有另一个更好的解决方案。

@deanveloper

然而,有时(如在结构初始化的情况下)拆分成多个函数并不是一个好主意。

是的,但也不需要以不同的方式装饰它们,因为所有必需的特定装饰都来自parseField

我认为还有另一个更好的解决方案。

这很有可能! 如果我看到更好的解决方案,我会在一分钟内放弃try :)

大多数情况下,您需要对函数中的所有错误使用相同的修饰,因为应该期望子函数提供自己的上下文

@faiface我强烈不同意这个说法。 每个函数都是调用堆栈上另一个函数的子函数。 这意味着它在错误处理流程中具有相同的职责(即为上层范围提供错误上下文)。

想象一个将两个数据块附加到单个文件的函数。 如果您几乎不返回“无法写入文件”语句,您将如何区分这些附加中的哪一个失败?

我们都是懒惰的生物。 我也是,如果可以的话,我宁愿做一次一劳永逸的事情。 是的,当我开始使用 Go 进行冒险时,我认为错误处理有点麻烦。 经过几年的实践,虽然我的观点发生了 180 度的转变。 我相信 Go 中当前的错误处理促进了负责任的编程和良好的设计。 恕我直言,添加另一种破坏这种方法的机制将是一个巨大的失败。

@mklimuk我评论的一个关键部分是“大部分时间”。 您提供的示例可能最好由if err != nil 。 正如多次提到的, try并非旨在处理所有情况,只是最常见的情况。

证据表明它确实这样做了,因为标准库中所有错误处理代码的 70% 可以使用try开箱即用,同样适用于标准库中所有错误处理代码的 59%荒野。

@faiface好吧, try可以替换显式错误处理的事实并不意味着它应该。 在我的情况下,返回错误而不添加上下文并不是“最常见的情况”。 恰恰相反:)

支持该线程的人们只是担心,为了使 Go 代码不那么冗长,这个新声明会破坏原始 Go 设计背后的全部努力(简单性、清晰性等)。

当然,但我希望你明白try不是为了在没有上下文的情况下返回错误。 事实上,最常见的添加上下文的情况(每个函数一个上下文)被try大大简化了:

func doSomething() (err error) {
    defer fmt.HandleErrorf(&err, "doing something")

    x := try(oneThing())
    try(anotherThing(x))
    // ...
}

需要意识到的是,在大多数情况下, oneThing()anotherThing()会自行返回足够的上下文,因此将其包装在一个简单的"doing something: ..."就完全足够了。

作为旁注,我认为我们可以对 _who_ 进行装饰使用一些约定。 在 stdlib 中,一些函数执行此操作,例如copy: x to y或类似的,我个人一直将装饰留给调用者,因为它有参数。

例如,如果我有一个Copy()我会做类似return errors.Wrap(err, "writing")事情,并且使用Copy()的调用者会用errors.Wrapf(err, "copying from %v to %v", src, dst)或类似的东西换行。

这两个不能很好地混合和匹配,有时最终会出现重复的字符串,是否最好只说 stdlib 样式是惯用的? 不过,我不记得他们都像这样。 我认为这是@faiface的例子就足够了的唯一方法。 也许我一直在颠倒这个问题,对我来说,做更少的事情并将决定留给调用者感觉更干净,尤其是“以防万一”他们省略了上下文信息。

我个人一直把装饰留给调用者,因为它有参数。

是的。 例如,考虑一个从 HTTP 请求中解析出 JSON 正文、检查标头等的函数。 如果它收到语法上无效的 JSON,它的责任——它所能做的,真的——就是报告错误。 _caller_ 知道 API 的哪一部分试图被调用,并且需要相应地修饰错误,然后依次将其传递到链上或发出 HTTP 错误。

如果您的函数真正分解出可以在多个地方使用的通用代码,它们将没有修饰错误所需的信息。 相反,如果它们确实拥有所有上下文,它们可能并不是真正意义上的独立函数,您只是创建函数来分解代码并使其看起来比实际更有条理。

@lpar介意举个具体的例子吗?

我已经举了一个具体的例子? 考虑一下您的 parseJSON 函数是否真的知道上下文并且能够修饰它正在解析主体的 API 端点和活动流。 这将表明它特定于该端点,或者您传递信息只是为了您可以使用它来包装错误。

@lpar好的,这是if err != nil将继续使用的另一个例子。 或者您将逻辑拆分为多个功能。

但是请理解,举一个try不合适的例子并不是反对try的论据。 try并不是要取代所有的错误处理,只是最常见的情况。

Screenshot 2019-07-07 at 6 30 42 PM

@abejide001 try提议不是许多其他语言的传统“try/catch”,它更类似于 Rust 中的try!宏。 很好的模因,哈哈

糟糕 - 发布到错误的问题。 移至https://github.com/golang/go/issues/32437#issuecomment -509024693。

我最近发布了一个 #32968 提案,该提案建立在我不同意try宏拥有的危险嵌套能力的基础上。 虽然我希望它没有严重的缺陷,但作为一个作者,我不适合看到任何缺陷。 所以我想请我的_不尝试_阵营(你:)来看看,评估和评论它。


摘抄:

  • _ check不是单行的:它在许多重复的地方最有帮助
    使用相同表达式的检查应紧邻执行。_
  • _它的隐式版本已经在操场上

设计约束(满足)

它是内置的,它不嵌套在一行中,它允许比try多得多的流程,并且对其中的代码形状没有期望。 它不鼓励裸回报。

使用示例

// built-in 'check' macro signature: 
func check(Condition bool) {}

check(err != nil) // explicit catch: label.
{
    ucred, err := getUserCredentials(user)
    remote, err := connectToApi(remoteUri)
    err, session, usertoken := remote.Auth(user, ucred)
    udata, err := session.getCalendar(usertoken)

  catch:               // sad path
    ucred.Clear()      // cleanup passwords
    remote.Close()     // do not leak sockets
    return nil, 0, err // dress before leaving
}
// happy path

// implicit catch: label is above last statement
check(x < 4) 
  {
    x, y = transformA(x, z)
    y, z = transformB(x, y)
    x, y = transformC(y, z)
    break // if x was < 4 after any of above
  }

希望这有帮助,享受!

但是请理解,举一个try不合适的例子并不是反对try的论据。 try并不是要取代所有的错误处理,只是最常见的情况。

根据我之前发布的统计数据,它们不是我代码中最常见的情况。 我的代码中最常见的情况是在返回之前包装错误。 所以try最多只适用于我的错误返回的个位数百分比(*),这就是为什么我认为我们需要更好的东西。

(*) 事实上,我倾向于认为裸err返回实例可能是应该修复的错误。

完全同意,不要管“if err != nil”。

@abejide001 try提议不是许多其他语言的传统“try/catch”,它更类似于 Rust 中的try!宏。 很好的模因,哈哈

我只关心这一点,Go 对新手来说已经是一门陌生的语言,现在我们必须解释为什么try有定制的逻辑。 FWIW,我不认为说“Rust 做到了”是证明在语言中添加任何内容的合理理由——它只是不为人所知。

@因为我说这不是为了证明该功能的合理性,我只是说它是为了澄清该功能的作用。 我对try提案持中立态度。

并不是说这有什么不同,但它也是 Swift 中的一个特性,尽管使用关键字而不是宏。

对于究竟尝试实现什么目标,似乎存在一些混淆。 恕我直言,问题不在于写入多个检查错误的 if 块。 你写一次就完成了。 问题在于读取具有多个这些块的代码。 我们做的阅读比写作多得多。 这些块混淆了实际的代码,因为它们与它交织在一起。 更糟糕的是,很多时候它们几乎完全相同,在 if 块中的某处只有很小的字符串差异。

我个人更喜欢旧的支票处理草案,但这至少可以很好地分离错误和业务路径。 我们可能最终能够拥有一个单一的函数作用域上下文,而不是为每个调用,目前很有可能重复与父错误相同的事情。

@icholy写道:

有压倒性的社区反馈要求更简化的错误处理(来自年度调查)。 Go 团队现在正在解决这个问题。

我只是在这里了调查: //blog.golang.org/survey2018-results

显然,问题是:“您今天使用 Go 个人面临的最大挑战是什么?” 可能的答案是“错误处理”。

我很想知道如何根据这个问题+答案推断出需要更简短的语法。 我可能也回答了“错误处理”,但绝不是我想看到另一种语法。 如果我在调查中选中了这个选项,我会想到更好地允许包装错误,为它们提供堆栈跟踪等。

我的建议是退出所有错误处理建议(实际上是@miekg 的建议)。 首先确定社区真正想要的是什么,并记录下来。 然后找出他们想要的原因。 然后才开始寻找实现这一目标的方法。

我刚刚经历了 try 提案,但除非我遗漏了一些东西,否则它会忽略_为什么_它被提议,除了“消除样板 if 语句 [...}”。 但是没有提到为什么需要删除这些样板文件 if 语句。

我绝对同意上面的说法。 让我们看看新的错误值更改是否有助于帮助人们处理 Go 的错误处理投诉。 然后我们可以看看是否需要更简短的语法。

这里的人反对try是因为他们认为所有返回的错误都应该被注释。 现实情况是,在当前的代码库(包括标准库)中,很大一部分错误检查具有 ~bare~ 未注释的错误返回,并且会受益于try 。 你的代码应该如何信仰无关的代码这样的。 饶了我你的教条吧。

@icholy忽略错误表明开发人员不关心该错误。 该错误是无关紧要的,或者调用者认为是不可能的。 如果是这种情况,那么“尝试”同样毫无意义,调用者根本不会将函数包装在“尝试”中。

我的建议是退出所有错误处理建议(实际上是@miekg 的建议)。 首先确定社区真正想要的是什么,并记录下来。 然后找出他们想要的原因。 然后才开始寻找实现这一目标的方法。

我强烈同意这一点。 对于 Go 错误处理的任何改进甚至应该支持哪些功能,我看到了很多基本的分歧。 人们提到的每一个不同的功能块都引发了对其命名和语法的讨论,因此讨论无处可去。

我想更详细地了解更广泛的 Go 社区实际上希望从任何提议的新错误处理功能中获得什么。

我整理了一份调查,列出了一些不同的功能,我看到人们提出了一些错误处理功能。 我已经仔细_省略_了任何提议的命名或语法,当然,我试图使调查保持中立,而不是支持我自己的意见。

如果人们想参与,这里是链接,缩短分享:

https://forms.gle/gaCBgxKRE4RMCz7c7

参与的每个人都应该能够看到汇总结果。 然后,也许一旦我们更好地了解人们真正想要什么,我们将能够就 try 提案是否提供这些东西进行明智的讨论。 (然后甚至可能继续讨论语法。)

@lane-c-wagner 你是想说返回一个未注释的错误与根本不返回它是一样的吗? 编辑:修复以前的评论

@icholy啊,我误解了。 当你说“裸”时,我以为你的意思是“_”忽略了错误。

该提案认为,任何行动都不应是有效行动。 此更改会影响该语言的所有用户,因为他们会阅读代码。 因此,一项确定最大障碍的调查仍然需要询问社区这个障碍是否值得修复。 这个提议是对这样一个问题的最接近的评价。

请不要再说“每个人都可以忽略” try 。 我们阅读他人编写的代码。

@tv42我不知道你是不是在这里对我说话,但我也说过,你有道理。 罪有应得。 我将尝试对诸如此类的概括更加小心。 谢谢。

@griesemer你的调查严重缺乏。 我投票支持错误处理,但我的意思是完全类型安全,而不是冗长。 你最好再写一个关于错误的。

我仍然想要和类型。

这是一个关于 gofmt 当前格式化方式的建议 if err != nil

(这不是对 try() 提案的意见。)

当 if 语句返回一行非零错误值时,例如:

err := myFunc()
if err != nil {
    return err
}

gofmt 可以放宽自己的 if 语句规则并将其格式化为一行,如下所示:

err := myFunc()
if err != nil { return err }

三行错误处理代码变成了一行。 少杂乱。 更容易遵循程序流程。

需要对在哪里划线(双关语承认)进行一些判断
政府规则改变。 它可能包括一些装饰,例如:

err := myFunc()
if err != nil { return fmt.Errorf("myFunc() blew up! %v", err }

但是精心设计的多行错误处理应该保持原样:多行且清晰明确。

_try_ 提案已被撤回: https :

泛型有人吗?

这是一个关于 gofmt 当前格式化方式的建议 if err != nil

我已经试过了,恕我直言,这种方式比多行格式更不可读。 尝试比那个解决方案好得多。

IMO 这里的问题不是错误处理是如何执行的,而是它是否被忽略。 难道不能保持if err != nil语法不变,但限制对Error返回的无知吗? 就像使用遗留代码的 deseverity 选项使其成为编译器警告/错误。

IMO 这里的问题不是错误处理是如何执行的,而是它是否被忽略。 难道不能保持if err != nil语法不变,但限制对Error返回的无知吗? 就像使用遗留代码的 deseverity 选项使其成为编译器警告/错误。

许多人想要一个显示被忽略错误的短绒。

我更愿意把它变成一个严重的错误,但看看大量已经写好的遗产,linter 也是公平的。

我发现https://github.com/kisielk/errcheck对告诉我未处理的错误很有价值@plyhun @sorenvonsarvort

正如在#32437 上的讨论中看到的,这个提议实际上已经被接受了。 关闭。 如果问题再次出现,可以打开一个新的提案。

我开始认为,许多提案感觉不太合适的原因之一是因为它们实际上试图同时解决两个不同的问题。 一方面,确实在几乎每个函数调用之后都有err != nil块会以一种奇怪的方式打破代码流,虽然它肯定有它的优点,但我认为这只是一半问题。 另一个问题是处理多个返回,无论是否涉及错误,都可能非常笨重。

尽管两者之间看似很小的差异,但多个返回函数与单个返回函数感觉非常非常不同。 这有点像对使用多个参数的调用函数有额外的限制。 有时处理起来感觉很奇怪。 当您调用具有多个返回值的函数时,您几乎总是需要在自己的行上这样做,并且它与:=结合使用,通常是其他地方讨论过的各种变量阴影问题的主要来源. 您不能将方法调用链接到它们上,您不能从它们直接分配给同一行上的结构字段和新变量,等等。

我不知道。 也许这只是我。 但是我已经使用 Go 将近 10 年了,有时调用具有多个返回值的函数仍然让我感到有些尴尬。

谢谢!

if err != nil存在一个实际问题, err可能比它应该的寿命更长。 当您内联if它解决了问题,但并非所有情况都可以内联。

if err := foo(); err != nil {
if _, err := bar(); err != nil {



md5-6a135eb952fe7b24b3389cb16d3244a1



a, err := bar()
if err != nil {



md5-d52f811d3e31bb368bd8045cfb2e93b4



var err error
baz.A, err = bar()
if err != nil {

if err != nil {}块完成后, err变量不应存在于函数作用域中。 这是我的提案,它建立在try()提案的基础上,以解决该问题https://github.com/golang/go/issues/33161。 我希望得到一些建设性的反馈。

在 if err != nil {} 块完成后,函数作用域中不应存在 err 变量。

为什么在 if 块完成后它“应该”不存在? 编译器可以对其进行优化(如果它认为有必要的话),并且当 err := stmt()\nif err != nil {} 块完成时没有精神负担,因为这些几乎总是在一起。

我还没有深入研究你的提案(尽管 kudo 是为了努力写一个!)。 但是,正如我在上面的评论中也概述的那样,我认为在我们深入研究解决问题的任何建议之前,需要对任何感知到的问题进行更多的研究。

@Freeaqingme错误在if err != nil块完成后不应该存在,主要是因为我们已经表现得好像没有。

在 CopyFile 示例中,有r, err := os.Open(src)后跟w, err := os.Create(dst) 。 第二个err遮蔽了第一个。 阴影变量通常不受欢迎。

还有其他一些奇怪的地方。 如果我有err := foo()和后来的类似bar.V, err = baz() ,如果代码被重构并且我不再需要 foo() 我需要在baz之前添加var err error baz行。 . 我不认为重构函数中的不同位置应该影响其他地方。

从技术上讲

    r, err := os.Open(src)
    if err != nil {
        return ...
    }
    w, err := os.Create(dst)

err的第二个实例不影响第一个实例。 它们实际上是同一个变量。 请参阅https://golang.org/ref/spec#Short_variable_declarations 上关于重新声明变量的讨论

func doSomeThing() {
r,错误:= os.Open(文件名)
恐慌(fmt.Errorf(错误,“无法打开文件:%s”,文件名))//
这里很恐慌。

}

2019 年 10 月 10 日星期四上午 11:24 clearcode [email protected]写道:

我认为我们可以添加一个内置函数:

断言()

例子:

func doSomeThing() 错误 {

r, err := os.Open(filename)
assert(err, "failed to open file: %s", filename) // in this step, just return the error

响应,错误:= http.Get(someURL)
断言(错误,“请求失败”)

}

和另一个不返回错误的函数:

func doSomeThing() {
r,错误:= os.Open(文件名)
assert(err, "failed to open file: %s", filename) // 这里很恐慌。

}

所以 assert(error, args ...interface{}) 比: if err != nil 更好; {
返回错误 }


您收到此消息是因为您发表了评论。
直接回复本邮件,在GitHub上查看
https://github.com/golang/go/issues/32825?email_source=notifications&email_token=AGUV7XQ5HO7GL3YP72R7BV3QN2N55A5CNFSM4H4DL33KYY3PNVWWK3TUL52HS4DFVREXG43MVVBWJLOZEAST20000WWWWWWZGO32002G43VMVBWJLOZEGA20200000WWWWZGO3200000WWWWWZGO32000007GL3YP72R7BV3
或取消订阅
https://github.com/notifications/unsubscribe-auth/AGUV7XS4JMK44QHIIR3RSGTQN2N55ANCNFSM4H4DL33A
.

外卖是我想看到当前返回的实际错误
当前行中的函数。

2019 年 10 月 11 日星期五上午 9:55 Aaaa Einai [email protected]写道:

func doSomeThing() {
r,错误:= os.Open(文件名)
panic(fmt.Errorf(err, "failed to open file: %s", filename)) // 这里是 panic。

}

2019 年 10 月 10 日星期四上午 11:24 clearcode [email protected]
写道:

我认为我们可以添加一个内置函数:

断言()

例子:

func doSomeThing() 错误 {

r, err := os.Open(filename)
assert(err, "failed to open file: %s", filename) // in this step, just return the error

响应,错误:= http.Get(someURL)
断言(错误,“请求失败”)

}

和另一个不返回错误的函数:

func doSomeThing() {
r,错误:= os.Open(文件名)
assert(err, "failed to open file: %s", filename) // 这里很恐慌。

}

所以 assert(error, args ...interface{}) 比: if err != nil 更好; {
返回错误 }


您收到此消息是因为您发表了评论。
直接回复本邮件,在GitHub上查看
https://github.com/golang/go/issues/32825?email_source=notifications&email_token=AGUV7XQ5HO7GL3YP72R7BV3QN2N55A5CNFSM4H4DL33KYY3PNVWWK3TUL52HS4DFVREXG43MVVBWJLOZEAST20000WWWWWWZGO32002G43VMVBWJLOZEGA20200000WWWWZGO3200000WWWWWZGO32000007GL3YP72R7BV3
或取消订阅
https://github.com/notifications/unsubscribe-auth/AGUV7XS4JMK44QHIIR3RSGTQN2N55ANCNFSM4H4DL33A
.

坦率地说,我不想要try提供的隐式回报。 如果我们有泛型,我更喜欢使用 monad-ish 行为的解决方案。

type Result<T> interface {
  Expect(err error) T
  OrElse(defaultValue T) T
}

func From<T>(value T, err error) Result<T> { ... }

对我来说,这比目前提议的内置函数要干净得多,尽管需要对上述内容进行进一步的更改,因为您将有大量返回 (value, error) 和 Result 的方法

它与 Rust 的 Ok 和 Err 非常相似。
我认为if err != nil {}可能更好一点。

@Yanwenjiepy这是故意的,我是 Rust 的Result类型的忠实粉丝。

我学习围棋的时间不到 10 分钟。 在我查看的代码中,我注意到的第一件事就是一遍又一遍地粘贴这个副本:

someValue, err := someFunction();
if err != nil {
  panic(err)
}

我显然不是专家,但它可能是有价值的,我只是第一眼看到这个线程就结束了。

那是因为您正在查看代码片段以进行学习。 真正的代码必须处理错误,而不仅仅是恐慌和崩溃。

没错,但错误可以(并且通常应该)分组。 这就是其他语言中存在 try/catch 块的原因。 例如,对我来说,以下闻起来不像恐龙:

try {
  foo, throw err := someFunction();
  bar, throw err := foo.get();
  baz, throw err := bar.make();
  qux, throw err := baz.transform();
} catch(err) {
  // "Unable to foo bar baz qux."
  tryHarder();
}

再一次,完全外行。 但是代码只是符号,如果它们重复得足够多,你也可以为它制作一个符号。 这似乎是一个非常频繁重复的符号。

您可能需要查看 Rob Pike 的Errors Are Values帖子,以了解如何使用帮助程序合并错误并一次性处理所有错误。 在实践中,使用单个子句捕获所有异常在大多数拥有它们的语言中被认为是糟糕的风格,因为你最终隐藏了关于实际发生的信息。 (并且如果您扩展示例以打破单个捕获的异常而不丢弃该信息,则代码最终与 Go 等效项一样长。)

谢谢你的链接。 errWriter是一个完全可以通过的解决方案。

没错,但错误可以(并且通常应该)分组。 这就是其他语言中存在 try/catch 块的原因。 例如,对我来说,以下闻起来不像恐龙:

try {
  foo, throw err := someFunction();
  bar, throw err := foo.get();
  baz, throw err := bar.make();
  qux, throw err := baz.transform();
} catch(err) {
  // "Unable to foo bar baz qux."
  tryHarder();
}

再一次,完全外行。 但是代码只是符号,如果它们重复得足够多,你也可以为它制作一个符号。 这似乎是一个非常频繁重复的符号。

假设每个函数返回重叠错误类型,并且您必须优雅地处理所有函数结果,您如何编写 tryHarder()?

try {
  foo, throw err := someFunction();  // err could be TypeA and TypeB
  bar, throw err := foo.get();       // err could be TypeB and TypeC
  baz, throw err := bar.make();      // err could be TypeA and TypeC
  qux, throw err := baz.transform(); // err could be TypeB and TypeD
} catch(err) {
  tryHarder(); // tell me how to handle each error?
}

其他人只需要 1 分钟就可以理解以下代码:

foo, err := someFunction();  // err could be TypeA and TypeB
if err != nil {
 // handle err
}

bar, err := foo.get();       // err could be TypeB and TypeC
if err != nil {
  // handle err
}

baz, err := bar.make();      // err could be TypeA and TypeC
if err != nil {
  // handle err
}

qux, err := baz.transform(); // err could be TypeB and TypeD
if err != nil {
  // handle err
}

假设每个函数返回重叠错误类型,您必须优雅地处理所有函数结果

在那个例子中,你是完全正确的。

此页面是否有帮助?
0 / 5 - 0 等级