天天看点

Go语言 错误处理

文章目录

    • 导言
    • 错误处理
      • 错误是什么?
      • 例子
      • 错误的类型表示
      • 从错误中提取更多信息
        • 方法 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

行,我们将匹配的文件名输出。

程序输出如下:

从输出中可以看出,此时没有

与模式串匹配的

文件名。但实际上,这是因为模式串

格式错误

导致的。

由于忽略了错误处理,对于上面的输出,我们不能确定:到底是没有

与模式串匹配的

的文件名,还是

出现了错误

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

这就是全部内容了~

祝你每天都开心~

原作者留言

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

最后

感谢原作者的优质内容。

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