Go语言 错误处理


导言

  • 原文链接: 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

正如其它的内建类型,如 intfloat64… 我们可以把错误存储在变量中,也可以让函数返回错误。


例子

接下来,我将写一个程序,这个程序试图打开一个不存在的文件。

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包,它的定义如下:

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

Open函数存在 2 种情况:

  • 如果文件被成功打开,Open函数 会返回文件句柄,此时错误为 nil
  • 如果在打开文件过程中遇到错误,Open函数 会返回 nil文件句柄,此时错误不为 nil

在一般情况下,如果函数需要返回错误,错误必须位于最后一个返回值。根据这个规则,Open函数 将 err 作为最后一个返回值。

在第 9 行,我们试图打开路径为 /test.txt 的文件。

在第 10 行,我们将 errnil 进行比较。如果 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 {  
    ... 
}

DNSErrorTimeout() boolTemporary() bool方法,分别会告诉我们:错误是否是超时产生的、错误是否是临时的。

接下来,我们来写个程序。在程序中,我们会先将错误断言为 *DNSError类型,之后通过调用 DNSError结构 的 Timeout() boolTemporary() 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,我们获取到了错误接口的底层对象。

在第 1113行,我们分别判断错误是否是超时产生的,以及是否是临时。

程序输出如下:

generic error:  lookup golangbot123.com: no such host  

上面的例子也告诉我们:我们可以根据错误的产生原因,对症下药。


方法 3

获得错误更多信息的第 3 种方法是:直接比较。

让我们通过例子来理解这种方法。

filepath包 有一个 Glob 函数,这个函数能返回一些 与模式串匹配的 文件名。当模式串格式错误时,Glob函数 会返回一个错误变量 — ErrBadPattern

ErrBadPattern变量 被定义在 filepath包 中。

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)
}

在上面的程序中,模式串[ 的格式是错误的。

在第 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 行,我们将匹配的文件名输出。

程序输出如下:

matched files []  

从输出中可以看出,此时没有 与模式串匹配的 文件名。但实际上,这是因为模式串 格式错误 导致的。

由于忽略了错误处理,对于上面的输出,我们不能确定:到底是没有 与模式串匹配的 的文件名,还是 出现了错误

所以,请不要忽略错误处理!!!

这就是全部内容了~

祝你每天都开心~


原作者留言

优质内容来之不易,您可以通过该 链接 为我捐赠。


最后

感谢原作者的优质内容。

欢迎指出文中的任何错误。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值