Go 语言通过内置的错误接口提供了非常简单的错误处理机制。
让我们再深入一点,看看如何定义错误类型的构建。错误是一个带有以下定义的接口类型,
type error interface {
Error() string
}
2
3
它包含一个带有Error()字符串的方法。任何实现这个接口的类型都可以作为一个错误使用。这个方法提供了对错误的描述。
当打印错误时,fmt.Println函数在内部调用Error() 方法来获取错误的描述。这就是错误描述是如何在一行中打印出来的。
从错误中提取更多信息的不同方法
既然我们知道错误是一种接口类型,那么让我们看看如何提取更多关于错误的信息。
在上面的例子中,我们仅仅是打印了错误的描述。如果我们想要的是导致错误的文件的实际路径。一种可能的方法是解析错误字符串。这是我们程序的输出,
open /test.txt: No such file or directory
我们可以解析这个错误消息并从中获取文件路径"/test.txt"。但这是一个糟糕的方法。在新版本的语言中,错误描述可以随时更改,我们的代码将会中断。
是否有办法可靠地获取文件名?答案是肯定的,它可以做到,标准Go库使用不同的方式提供更多关于错误的信息。让我们一看一看。
1.断言底层结构类型并从结构字段获取更多信息
如果仔细阅读打开函数的文档,可以看到它返回的是PathError类型的错误。PathError是一个struct类型,它在标准库中的实现如下,
type PathError struct {
Op string
Path string
Err error
}
func (e *PathError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.Error() }
2
3
4
5
6
7
从上面的代码中,您可以理解PathError通过声明Error()string
方法实现了错误接口。该方法连接操作、路径和实际错误并返回它。这样我们就得到了错误信息,
open /test.txt: No such file or directory
PathError结构的路径字段包含导致错误的文件的路径。让我们修改上面写的程序,并打印出路径。
修改代码:
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")
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
在上面的程序中,我们使用类型断言获得错误接口的基本值。然后我们用错误来打印路径.这个程序输出,
File at path /test.txt failed to open
- 断言底层结构类型,并使用方法获取更多信息
获得更多信息的第二种方法是断言底层类型,并通过调用struct类型的方法获取更多信息。
示例代码:
type DNSError struct {
...
}
func (e *DNSError) Error() string {
...
}
func (e *DNSError) Timeout() bool {
...
}
func (e *DNSError) Temporary() bool {
...
}
2
3
4
5
6
7
8
9
10
11
12
13
从上面的代码中可以看到,DNSError struct有两个方法Timeout() bool和Temporary() bool,它们返回一个布尔值,表示错误是由于超时还是临时的。
让我们编写一个断言*DNSError类型的程序,并调用这些方法来确定错误是临时的还是超时的。
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)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
在上面的程序中,我们正在尝试获取一个无效域名的ip地址,这是一个无效的域名。golangbot123.com。我们通过声明它来输入*net.DNSError来获得错误的潜在价值。
在我们的例子中,错误既不是暂时的,也不是由于超时,因此程序会打印出来,
generic error: lookup golangbot123.com: no such host
如果错误是临时的或超时的,那么相应的If语句就会执行,我们可以适当地处理它。
3.直接比较
获得更多关于错误的详细信息的第三种方法是直接与类型错误的变量进行比较。让我们通过一个例子来理解这个问题。
filepath包的Glob函数用于返回与模式匹配的所有文件的名称。当模式出现错误时,该函数将返回一个错误ErrBadPattern。
在filepath包中定义了ErrBadPattern,如下所述:
var ErrBadPattern = errors.New("syntax error in pattern")
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)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
运行结果:
syntax error in pattern
不要忽略错误
永远不要忽略一个错误。忽视错误会招致麻烦。让我重新编写一个示例,该示例列出了与模式匹配的所有文件的名称,而忽略了错误处理代码。
package main
import (
"fmt"
"path/filepath"
)
func main() {
files, _ := filepath.Glob("[")
fmt.Println("matched files", files)
}
2
3
4
5
6
7
8
9
10
11
我们从前面的例子中已经知道模式是无效的。我忽略了Glob函数返回的错误,方法是使用行号中的空白标识符。
matched files []
由于我们忽略了这个错误,输出看起来好像没有文件匹配这个模式,但是实际上这个模式本身是畸形的。所以不要忽略错误。