APP下载

GCTT 出品Go 系列教程——30. 错误处理

消息来源:baojiabao.com 作者: 发布时间:2024-05-16

报价宝综合消息GCTT 出品Go 系列教程——30. 错误处理

Go语言中文网,致力于每日分享编码、开源等知识,欢迎关注我,会有意想不到的收获!

Go 系列教程是非常棒的一套初学者教程,入门就它了。

这是 Golang 系列教程中的第 30 篇。在本章教程中,我们将讨论 Go 语言中的错误处理。

什么是错误?

错误表示程式中出现了异常情况。比如当我们试图开启一个档案时,档案系统里却并没有这个档案。这就是异常情况,它用一个错误来表示。

在 Go 中,错误一直是很常见的。错误用内建的 error 型别来表示。

就像其他的内建型别(如 int、float64 等),错误值可以储存在变数里、作为函式的返回值等等。

示例

现在我们开始编写一个示例,该程式试图开启一个并不存在的档案。

在程式的第 9 行,我们试图开启路径为 /test.txt 的档案(playground 显然并不存在这个档案)。os 包里的 Open 函式有如下签名:

func Open(name string) (file *File, err error)

如果成功开启档案,Open 函式会返回一个档案控制代码(File Handler)和一个值为 nil 的错误。而如果开启档案时发生了错误,会返回一个不等于 nil 的错误

如果一个函式方法 返回了错误,按照惯例,错误会作为最后一个值返回。于是 Open 函式也是将 err 作为最后一个返回值。

按照 Go 的惯例,在处理错误时,通常都是将返回的错误与 nil 比较。nil 值表示了没有错误发生,而非 nil 值表示出现了错误。在这里,我们第 10 行检查了错误值是否为 nil。如果不是 nil,我们会简单地打印出错误,并在 main 函式中返回。

执行该程式会输出:

open /test.txt: No such file or directory

很棒!我们得到了一个错误,它指出该档案并不存在。

错误型别的表示

让我们进一步深入,理解 error 型别是如何定义的。error 是一个界面型别,定义如下:

error 有了一个签名为 Error() string 的方法。所有实现该界面的型别都可以当作一个错误型别。Error() 方法给出了错误的描述。

fmt.Println 在打印错误时,会在内部呼叫 Error() string 方法来得到该错误的描述。上一节示例中的第 11 行,就是这样打印出错误的描述的。

从错误获取更多资讯的不同方法

现在,我们知道了 error 是一个界面型别,让我们看看如何从一个错误获取更多资讯。

在前面的示例里,我们只是打印出错误的描述。如果我们想知道这个错误的档案路径,该怎么做呢?一种选择是直接解析错误的字串。这是前面示例的输出:

open /test.txt: No such file or directory

我们解析了这条错误资讯,虽然获取了发生错误的档案路径,但是这种方法很不优雅。随着语言版本的更新,这条错误的描述随时都有可能变化,使我们程式出错

有没有更加可靠的方法来获取档名呢?答案是肯定的,这是可以做到的,Go 标准库给出了各种提取错误相关资讯的方法。我们一个个来看看吧。

1. 断言底层结构体型别,使用结构体字段获取更多资讯

如果你仔细阅读了 Open 函式的文件,你可以看见它返回的错误型别是 *PathError。PathError结构体型别,它在标准库中的实现如下:

如果你有兴趣了解上述源代码出现的位置,可以在这里找到:https://golang.org/src/os/error.go?s=653:716#L11。

通过上面的程式码,你就知道了 *PathError 通过宣告 Error() string 方法,实现了 error 界面。Error() string 将档案操作、路径和实际错误拼接,并返回该字串。于是我们得到该错误资讯:

open /test.txt: No such file or directory

结构体 PathError 的 Path 字段,就有导致错误的档案路径。我们修改前面写的程式,打印出该路径。

在上面的程式里,我们在第 10 行使用了型别断言(Type Assertion)来获取 error 界面的底层值(Underlying Value)。接下来在第 11 行,我们使用 err.Path 来打印该路径。该程式会输出:

File at path /test.txt failed to open

很棒!我们已经使用型别断言成功获取到了该错误的档案路径。

2. 断言底层结构体型别,呼叫方法获取更多资讯

第二种获取更多错误资讯的方法,也是对底层型别进行断言,然后通过呼叫该结构体型别的方法,来获取更多的资讯。

我们通过一个例项来理解这一点。

标准库中的 DNSError 结构体型别定义如下:

从上述程式码可以看到,DNSError 结构体还有 Timeout() bool 和 Temporary() bool 两个方法,它们返回一个布林值,指出该错误是由超时引起的,还是临时性错误。

接下来我们编写一个程式,断言 *DNSError 型别,并呼叫这些方法来确定该错误是临时性错误,还是由超时导致的。

在上述程式中,我们在第 9 行,试图获取 golangbot123.com(无效的域名) 的 ip。在第 10 行,我们通过 *net.DNSError 的型别断言,获取到了错误的底层值。接下来的第 11 行和第 13 行,我们分别检查了该错误是由超时引起的,还是一个临时性错误。

在本例中,我们的错误既不是临时性错误,也不是由超时引起的,因此该程式输出:

generic error: lookup golangbot123.com: no such host

如果该错误是临时性错误,或是由超时引发的,那么对应的 if 语句会执行,于是我们就可以适当地处理它们。

3. 直接比较

第三种获取错误的更多资讯的方式,是与 error 型别的变数直接比较。我们通过一个示例来理解。

filepath 包中的 Glob 用于返回满足 glob 模式的所有档名。如果模式写的不对,该函式会返回一个错误 ErrBadPattern。

filepath 包中的 ErrBadPattern 定义如下:

var ErrBadPattern = errors.New("syntax error in pattern")

errors.New() 用于建立一个新的错误。我们会在下一教程中详细讨论它。

当模式不正确时,Glob 函式会返回 ErrBadPattern。

我们来写一个小程式来看看这个错误。

在上述程式里,我们查询了模式为 [ 的档案,然而这个模式写的不正确。我们检查了该错误是否为 nil。为了获取该错误的更多资讯,我们在第 10 行将 error 直接与 filepath.ErrBadPattern 相比较。如果该条件满足,那么该错误就是由模式错误导致的。该程式会输出:

syntax error in pattern

标准库在提供错误的详细资讯时,使用到了上述提到的三种方法。在下一教程里,我们会通过这些方法来建立我们自己的自定义错误。

不可忽略错误

绝不要忽略错误。忽视错误会带来问题。接下来我重写上面的示例,在列出所有满足模式的档名时,我省略了错误处理的程式码。

我们已经从前面的示例知道了这个模式是错误的。在第 9 行,通过使用 _ 空白识别符号,我忽略了 Glob 函式返回的错误。我在第 10 行简单打印了所有匹配的档案。该程式会输出:

matched files []

由于我忽略了错误,输出看起来就像是没有任何匹配了 glob 模式的档案,但实际上这是因为模式的写法不对。所以绝不要忽略错误。

本教程到此结束。

这一教程我们讨论了该如何处理程式中出现的错误,也讨论了如何查询关于错误的更多资讯。简单概括一下本教程讨论的内容:

什么是错误?错误的表示获取错误详细资讯的各种方法不能忽视错误在下一教程,我们会建立我们自己的自定义错误,并给标准错误增加更多的语境(Context)。

祝你愉快。

上一教程 - “GCTT 出品”Go 系列教程——29. Defer

下一教程 - 自定义错误

历史文章:

“GCTT 出品”Go 系列教程——1. 介绍与安装

“GCTT 出品”Go 系列教程——2. Hello World

“GCTT 出品”Go 系列教程——3. 变数

“GCTT 出品”Go 系列教程——4. 型别

“GCTT 出品”Go 系列教程——5. 常量

“GCTT 出品”Go 系列教程——6. 函式(Function)

“GCTT 出品”Go 系列教程——7. 包

Go 系列教程——8. if-else 语句

“GCTT 出品”Go 系列教程——9. 循环

“GCTT 出品”Go 系列教程——10. switch 语句

“GCTT 出品”Go 系列教程——11. 阵列和切片

“GCTT 出品”Go 系列教程——12. 可变引数函式

“GCTT 出品”Go 系列教程——13. Maps

“GCTT 出品”Go 系列教程——14. 字串

“GCTT 出品”Go 系列教程——15. 指标

“GCTT 出品”Go 系列教程——16. 结构体,这一篇就够

“GCTT 出品”Go 系列教程——17. 超全的方法教程

“GCTT 出品”Go 系列教程——18. 界面(一)

“GCTT 出品”Go 系列教程——19. 界面(二)

“GCTT 出品”Go 系列教程——20. 并发入门

“GCTT 出品”Go 系列教程——21. Go 协程

“GCTT 出品”Go 系列教程——22. 通道(channel)

“GCTT 出品”Go 系列教程——23. 缓冲通道和工作池

“GCTT 出品”Go 系列教程——24. Select

“GCTT 出品”Go 系列教程——25. Mutex

“GCTT 出品”Go 系列教程——26. 结构体取代类

“GCTT 出品”Go 系列教程——27. 组合取代继承

“GCTT 出品”Go 系列教程——28. 多型

“GCTT 出品”Go 系列教程——29. Defer

2020-01-23 06:56:00

相关文章