30.错误处理

原文:https://golangbot.com/error-handling/

欢迎访问Golang 系列教程中的第30章。

什么是错误?

错误指程序中出现异常情况。比方说, 我们正在尝试打开一个文件, 文件系统中不存在该文档。这是一个异常情况, 它表示为一个错误。

Go中的错误是普通的旧值。错误使用内置error类型表示。

就像在type中构建的任何其他类型, 如 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")
}

在playground上运行

在上面的程序第9行中, 我们试图在路径/test.txt中打开该文件 (在操场上显然不存在)。os包的 Open 函数是这样定义的,

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

如果文件已成功打开, 则Open函数将返回文件处理程序, 错误将为零。如果打开文件时出现错误, 将返回一个非nil错误。

如果函数或方法返回一个错误, 则根据约定, 它必须是函数返回的最后一个值。因此, Open函数将err作为最后一个值返回。

GO中处理错误的惯用方式是将返回的错误与nil进行比较。是nil表示没有发生错误, 而非nil表示存在错误. 回到我们的例子本身, 我们在第10行判断err是否为nil。如果不是nil, 我们只需打印错误并从主函数返回。

运行此程序将打印

open /test.txt: No such file or directory  

完美��。我们得到一个错误, 指出该文件不存在。

错误类型的表示形式

让我们深入了解一下如何定义内置的error类型。error是具有以下定义的接口类型,

type error interface {  
    Error() string
}

它只包含一个名为Error() string的方法。任何实现此接口的类型都可以用作错误。此方法用来提供错误的说明。

打印错误时,fmt.Println函数调用时,会自动在内部调用Error() string方法以获取错误的说明。这就是在上述示例程序的11行中打印错误说明的方式。

从错误中提取更多信息的不同方法

现在我们知道error是一种接口类型,让我们看看我们如何提取更多关于错误的信息。

在上面看到的示例中, 我们刚刚打印了错误的描述。如果我们想要导致错误的文件的实际路径怎么办?一种可能的方法是解析错误字符串。这是我们的输出,

open /test.txt: No such file or directory  

我们可以解析这个错误信息,并得到导致错误的文件的文件路径“/test.txt”,但这是一个糟糕的方式。错误描述可以随时在新版本的语言中更改,我们的代码将会中断。

有没有办法可靠地获取文件名?答案是肯定的,它可以完成,标准的Go库使用不同的方式提供有关错误的更多信息。让我们一个一个看看他们。

1.断言底层结构类型并从结构字段获取更多信息

如果仔细阅读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() }  

如果您有兴趣知道上面的源代码在哪里, 可以在这里找到:https://golang.org/src/os/error.go?s=653:716#L11

从上面的代码中, 您可以理解*PathError通过声明Error() string方法来实现error interface。此方法将操作、路径和实际错误串联起来, 并返回它。因此, 我们得到了错误信息,

open /test.txt: No such file or directory 

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

在playground上运行

在上面的程序中, 我们使用第10行中的类型断言来获取错误接口的基础值。然后, 我们在11行使用 err err.Path来打印路径。这个程序输出,

File at path /test.txt failed to open  

棒极了��。我们已成功使用类型断言从错误中获取文件路径。

2. 断言底层结构类型并使用方法获取更多信息

获取更多信息的第二种方法是断言基础类型, 并通过调用结构类型上的方法获取更多信息。

让我们通过一个例子来更好地理解这一点。

标准库中的 DNSError 结构类型定义如下所示,

type DNSError struct {  
    ...
}

func (e *DNSError) Error() string {  
    ...
}
func (e *DNSError) Timeout() bool {  
    ... 
}
func (e *DNSError) Temporary() bool {  
    ... 
}

从上面的代码中可以看到, DNSError结构有两个方法Timeout() boolTemporary() 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)
}

注意: DNS 查找在操场上不起作用。请在本地计算机中运行此程序。

在上面的程序中, 我们试图在第9行中获取无效域名golangbot123.com的 ip 地址。在第10行中, 我们通过使用*net.DNSError来断言获取错误的基本值。然后, 我们在第11和第13行来检查错误是否是由于超时还是临时。

在我们的情况下, 错误既不是临时, 也不是由于超时, 因此程序将打印,

generic error:  lookup golangbot123.com: no such host  

如果错误是暂时的或由于超时, 则相应的 if 语句将执行, 我们可以适当地处理它。

3. 直接比较

获取有关错误的更多详细信息的第三种方法是直接与类型为error的变量进行比较。让我们通过一个例子来理解这一点。

filepath包的Glob函数用于返回与模式匹配的所有文件的名称。当模式格式不正确时, 此函数返回错误ErrBadPattern

ErrBadPatternfilepath包中的定义如下。

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

在playground上运行

在上面的程序中, 我们搜索模式为[的文件, 这是一个格式不正确的模式。我们检查错误是否为nil。为了获取有关错误的更多信息, 我们在第10行直接将其与filepath.ErrBadPattern进行比较。如果条件满足, 则错误是由于模式不正确造成的。该程序将打印,

syntax error in pattern  

标准库使用上述任一方法提供有关错误的更多信息。我们将在下一个教程中使用这些方法来创建我们自己的自定义错误。

不忽略错误

永远不要忽略错误。忽视错误是自找麻烦。让我重写示例, 其中列出了所有匹配模式 (忽略错误处理代码) 的文件的名称。

package main

import (  
    "fmt"
    "path/filepath"
)

func main() {  
    files, _ := filepath.Glob("[")
    fmt.Println("matched files", files)
}

在playground上运行

从上一示例中我们已经知道模式无效。第9行,调用Glob 函数,使用_空白标识符来忽略函数返回的错误。我只是打印的匹配文件在10行。该程序将打印,

matched files []  

因为我们忽略了错误, 所以输出看起来好像没有文件匹配模式, 但实际上模式本身是错误的。所以千万不要忽略错误。

这使我们到本教程的末尾。

在本教程中, 我们讨论了如何处理程序中发生的错误, 以及如何检查错误以获取更多信息。快速回顾一下我们在本教程中讨论的内容,

  • 什么是错误?
  • 错误表示
  • 从错误中提取更多信息的各种方法
  • 不忽略错误

在下一教程中, 我们将创建自己的自定义错误, 并将更多的上下文添加到标准错误中。

祝你今天开心。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值