Go中的错误处理

您好,哈布罗夫斯克市民! Golang Developer课程今天已经从OTUS开始我们认为这是分享有关该主题的另一篇有用文章的好机会。 今天让我们谈谈Go的错误处理方法。 让我们开始吧!



掌握Go代码中实用的错误处理




这篇文章是“开始之前”系列的一部分 ,我们在其中探索Golang的世界,分享在使用Go编写代码时应该了解的技巧和想法,这样您就不必自己花钱了。

我认为您已经至少具有Go的基本经验,但是如果您觉得在某个时候遇到了不熟悉的讨论材料,请不要犹豫,暂停一下,探索主题然后再回来。

现在我们已经扫清了道路,走吧!

Go的错误处理方法是最具争议和最被滥用的功能之一。 在本文中,您将学习Go的错误处理方法,并了解它们在“幕后”的工作方式。 您将学习几种不同的方法,查看Go源代码和标准库,以了解如何处理错误以及如何处理错误。 您将了解为什么类型断言在处理它们中起重要作用,并且您将看到即将在Go 2中引入的错误处理方面的更改。



参赛作品


首先,Go中的错误也不例外。 戴夫·切尼Dave Cheney)撰写了一篇有关此事的史诗般的博客文章 ,因此请您参考并总结一下:用其他语言,您无法确定函数是否会引发异常。 Go函数支持抛出多个值 ,而不是抛出异常,并且按照惯例,此功能通常用于返回函数的结果以及错误变量。



如果由于某种原因您的函数可能失败,则可能应该从中返回先前声明的error类型。 按照惯例,返回错误会向调用者发出有关该问题的信号,而返回nil不会被视为错误。 因此,您将使调用者理解问题的发生,并且他需要处理该问题:无论谁调用您的函数,他都知道在检查错误之前他不应该依赖结果。 如果错误不是nil,他必须检查并处理它(记录,返回,维护,调用某种重试/清除机制等)。


(3 //错误处理
5 //继续)

这些代码段在Go中非常常见,有些代码段将它们视为样板代码。 编译器将未使用的变量视为编译错误,因此,如果您不打算检查错误,则必须将它们分配给空标识符 。 但是,无论它有多方便,都不应忽略错误。


(4 //忽略错误是不安全的,在检查错误之前您不应该依赖结果)
在检查错误之前,结果不可信任

错误返回以及结果以及严格的Go类型系统使标记代码的编写大大复杂化。 除非已经检查了函数返回的错误,否则应始终假定函数的值已损坏,并且通过将错误分配给空标识符,可以明确地忽略函数的值可能已损坏。


空的身份证是黑暗的,充满了恐惧。

Go确实具有panicrecover机制,这在另一篇详细的Go博客文章中也有介绍。 但它们并非旨在模拟异常。 根据Dave的说法, “当您在Go中惊慌时,您真的会惊慌:这不是别人的问题,这已经是游戏玩家。” 它们是致命的,并会导致程序崩溃。 罗伯·派克(Rob Pike)提出了“别慌”的说法,这是不言而喻的:您可能应该避免使用这些机制,而是返回错误。

“错误就是含义。”
“不仅要检查错误,还要优雅地处理它们。”
“不要惊慌”
罗伯·派克的所有谚语

引擎盖下


错误界面

实际上,错误类型是一种使用一种方法简单接口 ,如果您不熟悉它,我强烈建议您在Go官方博客上查看此帖子


源错误界面

犯下自己的错误并不困难。 用户结构有多种方法可以实现Error() string方法。 实现此单一方法的任何结构都被视为有效的错误值,并且可以这样返回。

让我们看一下其中的一些方法。

内置errorString结构


错误接口最常用和最广泛的实现是内置的errorString结构。 这是您可以想到的最简单的实现。


来源: Go源代码

您可以在此处看到其简化的实现。 它所做的只是包含一个string ,并且此字符串由Error方法返回。 我们可以根据某些数据(例如,使用fmt.Sprintf来格式化此字符串错误。 但是除此之外,它不包含任何其他功能。 如果您应用了errors.Newfmt.Errorf ,那么您已经使用过它


(13 //输出:)

试一试

github.com/pkg/errors


另一个简单的示例是pkg / errors软件包。 不要与您先前了解的内置errors包混淆,该包提供了其他重要功能,例如错误包装,扩展,格式化和堆栈跟踪记录。 您可以通过运行go get github.com/pkg/errors安装软件包。



如果您需要将堆栈跟踪或必要的调试信息附加到错误中,则使用此程序包的NewErrorf可以提供已写入堆栈跟踪的错误,也可以使用它附加简单的元数据格式化功能。 Errorf实现了fmt.Formatter接口,即您可以使用fmt包的符文( %s%v%+v等)对其进行格式化。


(// 6或其他)

该软件包还介绍了errors.Wraperrors.Wrapf 。 这些函数使用消息和调用位置的堆栈跟踪为错误添加了上下文。 因此,您可以用上下文和重要的调试数据包装它,而不是简单地返回错误。



其他错误的错误包装器支持Cause() error方法,该方法返回其内部错误。 此外,它们还可以与errors.Cause(err error) error一起使用。 errors.Cause(err error) error功能,可提取包装错误中的主要内部错误。

错误处理


型式认可


类型断言在处理错误时起着重要的作用。 您将使用它们从接口值中提取信息,并且由于错误处理与error接口的用户实现相关联,因此error语句的实现是非常方便的工具。

它的语法就其所有用途而言都是相同的-如果x具有接口类型,则x x.(T)x.(T)指出x不是nil并且存储在x中的值是T类型的T 在接下来的几节中,我们将介绍两种使用类型语句的方式-特定的T类型和T类型的接口T


(2 //简化语法跳过布尔变量ok
3 //恐慌:接口转换:接口{}为nil,不是字符串
6 //使用布尔值确定扩展语法
8 //不会惊慌,而是在语句为false时将ok设置为false
9 //现在我们可以安全地使用s作为字符串了)

沙箱: 使用缩短的语法恐慌安全的扩展语法

附加的语法说明:类型断言可以与缩短的语法(在语句失败时会发生恐慌)或扩展的语法(使用逻辑值OK表示成功或失败)一起使用。 我总是建议拉长而不是缩短,因为我更喜欢检查OK变量,而不应对恐慌。


T型批准


类型为x.(T)的接口的类型为x.(T)语句确认x实现了T的接口T 因此,您可以保证接口值实现了该接口,并且只有在这样的情况下,您才可以使用其方法。


(5 ... //声称x实现了resolver接口
6 ... //在这里我们已经可以安全地使用此方法了)

要了解如何使用它,让我们再次看一下pkg/errors 。 您已经知道此错误包,因此让我们深入研究errors.Cause(err error) errorerrors.Cause(err error) error功能。

此函数接收到一个错误,并提取出它所遭受的最内层错误(该错误不再充当另一个错误的包装器)。 这看似原始,但您可以从此实现中学到很多东西:


来源: pkg /错误

该函数接收到错误值,并且不能假定其接收到的err参数是包装器错误( Cause方法支持)。 因此,在调用Cause方法之前,您需要确保正在处理实现此方法的错误。 通过在for循环的每次迭代中执行类型语句,可以确保cause变量支持Cause方法,并可以继续从中提取内部错误,直到找到不包含Cause的错误为止。

通过创建仅包含所需方法的简单本地接口,并在其上应用断言,可以将代码与其他依赖项分离。 您收到的参数不必是已知的结构,而只是一个错误。 任何实现ErrorCause方法的类型都可以。 因此,如果以错误类型实现Cause方法,则可以将此函数与它一起使用而不会降低速度。

但是,要牢记一个小缺陷:接口可能会发生更改,因此您应仔细维护代码,以免违反您的声明。 不要忘记在使用它们的位置定义接口,以保持它们的纤巧和整洁,您会满意的。

最后,如果只需要一种方法,有时在仅包含您依赖的方法的匿名接口上进行声明有时会更方便,即v, ok := x.(interface{ F() (int, error) }) 。 使用匿名接口可以帮助将代码与可能的依赖项分开,并保护其免受接口中可能的更改的影响。

T型和开关认证



在本节的开头,我将介绍两个类似的错误处理模式,这些模式有几个缺陷和陷阱。 这并不意味着它们并不常见。 在小型项目中,它们都是方便的工具,但是它们的伸缩性不好。

第一个是类型断言的第二种版本:执行具有特定类型T的类型x.(T)的断言。 他声称x的值为T类型,或者可以将其转换为T类型T


(2 //我们可以将v用作mypkg.SomeErrorType)

另一个是类型切换模式。 Type Switch使用保留的type关键字将switch语句与type语句组合在一起。 它们在错误处理中特别常见,在这种情况下,了解变量错误的基本类型可能非常有用。


(3 //处理中...
5 //处理中...)

两种方法的最大缺点是,它们都导致代码与它们的依赖项绑定。 两个示例都应该熟悉SomeErrorType结构(显然应该将其导出),并且应该导入mypkg包。
在这两种方法中,处理错误时,您都应该熟悉类型并导入其包。 当您处理包装器中的错误时,这种情况会更加严重,其中错误的原因可能是由您不知道也不应该知道的内部依赖性引起的错误。


(7 //处理中...
9 //处理中...)

类型开关区分*MyStructMyStruct 。 因此,如果您不确定要处理的是指针还是结构的实际实例,则必须提供两个选项。 此外,与常规交换机一样,Type Switch的情况不会失败,但是与普通Type Switch不同的fallthrough ,Type Switch禁止使用fallthrough ,因此您必须使用逗号并提供两个选项,这很容易忘记。



总结一下


仅此而已! 现在,您已经熟悉了这些错误,应该准备好修复Go应用程序可能抛出(或实际上返回)到路径的任何错误!
这两个errors包都提供了简单而重要的Go错误解决方法,如果它们满足您的需求,那么它们是一个不错的选择。 通过将它们与pkg/errors结合使用,您可以轻松实现自己的错误结构并利用Go错误处理的优势。

缩放简单错误时,正确使用类型语句可能是处理各种错误的好工具。 使用类型开关,或者通过验证错误的行为并检查其实现的接口,可以使用。

接下来是什么?


Go中的错误处理现在非常相关。 现在您已经具备了基础知识,您可能想知道我们处理Go错误的前途是什么!

Go 2的下一个版本对此非常关注,您已经可以查看草稿版本 。 此外,在dotGo 2019期间 Marcel van Lojuizen就一个我不能不推荐的话题进行了精彩的对话- “今天GO 2错误值”

显然,还有更多的方法,技巧和窍门,我不能在一篇文章中将它们全部包括在内! 尽管如此,我希望您喜欢它,并且在下一版“ 开始之前 !”中见!

现在传统上是在等待您的评论。

Source: https://habr.com/ru/post/zh-CN453806/


All Articles