文章目录
-
- 导言
- 错误处理
-
- 错误是什么?
- 例子
- 错误的类型表示
- 从错误中提取更多信息
-
- 方法 1
- 方法 2
- 方法 3
- 不要忽略错误
- 原作者留言
- 最后
导言
- 原文链接: Part 30: Error Handling
- If translation is not allowed, please leave me in the comment area and I will delete it as soon as possible.
错误处理
错误是什么?
错误就是程序中存在的异常情况。打开一个不存在的文件,这就是一个异常情况,也就是错误。
在Go
中,错误也是一个值,它的类型为 error
。 正如其它的内建类型,如
int
、
float64
… 我们可以把错误存储在变量中,也可以让函数返回错误。
例子
接下来,我将写一个程序,这个程序试图打开一个不存在的文件。
package main
import (
"fmt"
"os"
)
func main() {
f, err := os.Open("/test.txt")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(f.Name(), "opened successfully")
}
Open
函数 位于
os
包,它的定义如下:
Open
函数存在
2
种情况:
- 如果文件被成功打开,
函数 会返回文件句柄,此时错误为Open
。nil
- 如果在打开文件过程中遇到错误,
函数 会返回Open
文件句柄,此时错误不为nil
。nil
在一般情况下,如果函数需要返回错误,错误必须位于最后一个返回值。根据这个规则,函数 将
Open
作为最后一个返回值。
err
在第
9
行,我们试图打开路径为
/test.txt
的文件。
在第
10
行,我们将
err
与
nil
进行比较。如果
err
不等于
nil
,我们就输出错误。
运行程序,输出如下:
open /test.txt: No such file or directory
如我们所想,我们得到了一个错误,该错误告诉我们:文件不存在。
错误的类型表示
我们深入一下,看看
error
类型 是如何定义的。
type error interface {
Error() string
}
从上面可以看出:
error
是一个接口类型,它拥有一个
Error
方法。
任何实现了error
接口 的类型,都可以作为错误。 在上面的样例程序中,为了获取错误的描述信息,
fmt.Println
函数 内部会调用
Error() string
方法。
从错误中提取更多信息
接下来,我们看看怎么从错误中提取更多信息。
在上面的例子中,我们只是输出了错误的描述信息。假如我们需要错误中的文件路径,我们应该怎么做呢?
上面是什么意思呢?
意思就是:之前的错误的描述信息是
,而此时我们想要的是
open /test.txt: No such file or directory
。
/test.txt
其中一种方式就是:从错误的描述信息中 (描述信息是一个字符串),提取出文件路径。
但这是一种很烂的方式,因为随着版本更新,错误的描述信息可能会发生改变。
有没有方法能可靠地获取文件名呢?答案是肯定的,
通过Go
的标准库,我们能获取到错误的更多信息。 接下来,我将一个一个地列举。
方法 1
获得错误更多信息的第
1
种方法是:先断言错误的底层类型,再从该底层类型的字段中获得更多的信息。
如果你仔细阅读
Open
函数 的文档,你会发现:
Open
函数 会返回一个类型为
*PathError
的错误。
PathError
是一个结构体,它在标准库的实现如下:
type PathError struct {
Op string
Path string
Err error
}
func (e *PathError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.Error() }
通过声明
Error
方法,
*PathError
类型 实现了
error
接口。
PathError
结构 的
Path
字段 就是文件路径。
接下来,我们改写下之前的程序。在新的程序中,我们打印出错误信息的文件路径:
package main
import (
"fmt"
"os"
)
func main() {
f, err := os.Open("/test.txt")
if err, ok := err.(*os.PathError); ok {
fmt.Println("File at path", err.Path, "failed to open")
return
}
fmt.Println(f.Name(), "opened successfully")
}
在第
10
行,通过类型断言,我们得到了错误接口的底层对象。
程序输出如下:
File at path /test.txt failed to open
使用类型断言,我们成功地从错误中提取出文件路径。
方法 2
获得错误更多信息的第
2
种方法是:先断言错误的底层类型,再调用该底层类型的方法,进而获取更多信息。
通过例子理解吧~
在标准库中,
DNSError
结构 的定义如下:
type DNSError struct {
...
}
func (e *DNSError) Error() string {
...
}
func (e *DNSError) Timeout() bool {
...
}
func (e *DNSError) Temporary() bool {
...
}
DNSError
的
Timeout() bool
、
Temporary() bool
方法,分别会告诉我们:错误是否是超时产生的、错误是否是临时的。
接下来,我们来写个程序。在程序中,我们会先将错误断言为
*DNSError
类型,之后通过调用
DNSError
结构 的
Timeout() bool
、
Temporary() bool
方法,判断错误是否是超时产生的,以及是否是临时。
package main
import (
"fmt"
"net"
)
func main() {
addr, err := net.LookupHost("golangbot123.com")
if err, ok := err.(*net.DNSError); ok {
if err.Timeout() {
fmt.Println("operation timed out")
} else if err.Temporary() {
fmt.Println("temporary error")
} else {
fmt.Println("generic error: ", err)
}
return
}
fmt.Println(addr)
}
在上面程序的第
9
行,我们试着去获取一个非法域名 (
golangbot123.com
) 的
IP
地址。
在第
10
行,通过将错误接口断言为
*net.DNSError
,我们获取到了错误接口的底层对象。
在第
11
、
13
行,我们分别判断错误是否是超时产生的,以及是否是临时。
程序输出如下:
generic error: lookup golangbot123.com: no such host
上面的例子也告诉我们:我们可以根据错误的产生原因,对症下药。
方法 3
获得错误更多信息的第
3
种方法是:直接比较。
让我们通过例子来理解这种方法。
filepath
包 有一个
Glob
函数,这个函数能返回一些
与模式串匹配的文件名。当模式串格式错误时,
Glob
函数 会返回一个错误变量 —
ErrBadPattern
。
ErrBadPattern
变量 被定义在
filepath
包 中。
errors.New
会创建一个新的错误。
当模式串格式错误时,
Glob
函数 会返回
ErrBadPattern
变量。通过这个特点,我们来写个小程序。
package main
import (
"fmt"
"path/filepath"
)
func main() {
files, error := filepath.Glob("[")
if error != nil && error == filepath.ErrBadPattern {
fmt.Println(error)
return
}
fmt.Println("matched files", files)
}
在上面的程序中,模式串
[
的格式是错误的。
在第
10
行,为了获取更多错误信息,我们直接将
Glob
函数返回的错误 与
filepath.ErrBadPattern
变量 进行比较。如果它们相等,则表示
Glob
函数返回的错误 是 格式错误。
程序输出如下:
syntax error in pattern
标准库会通过以上提及的方法,为我们提供更多错误信息。当自定义错误时,我们也会用到这些方法。
不要忽略错误
不要忽略错误!忽略错误会带来麻烦!
接下来,我重写下上面的例子 (就是
Glob
函数 的那个例子)。这次,我们不进行错误处理。
package main
import (
"fmt"
"path/filepath"
)
func main() {
files, _ := filepath.Glob("[")
fmt.Println("matched files", files)
}
如前所述,模式串
[
是非法的。
在第
9
行,通过使用 空白标识符
_
,我们忽略了
Glob
函数返回的错误。
在第
10
行,我们将匹配的文件名输出。
程序输出如下:
从输出中可以看出,此时没有
与模式串匹配的文件名。但实际上,这是因为模式串
格式错误导致的。
由于忽略了错误处理,对于上面的输出,我们不能确定:到底是没有
与模式串匹配的的文件名,还是
出现了错误。
所以,请不要忽略错误处理!!!这就是全部内容了~
祝你每天都开心~
原作者留言
优质内容来之不易,您可以通过该 链接 为我捐赠。
最后
感谢原作者的优质内容。
欢迎指出文中的任何错误。