Go的错误处理

news/2024/11/30 5:58:41/

什么是错误?

错误表示程序中发生的任何异常情况。假设我们正在尝试打开一个文件,但该文件在文件系统中不存在。这是一种异常情况,表示为错误。

Go 中的错误是普通的旧值。就像任何其他内置类型(例如 int、float64 等)一样,错误值可以存储在变量中、作为参数传递给函数、从函数返回等。

错误使用内置error 类型表示。我们将在本教程后面详细了解该error类型。

例子

让我们立即开始尝试打开一个不存在的文件的示例程序。

package mainimport (  "fmt""os"
)func main() {  f, err := os.Open("/test.txt")if err != nil {fmt.Println(err)return}fmt.Println(f.Name(), "opened successfully")
}

Run in playground

在上面程序的第 9 步中,我们尝试打开路径中的文件/test.txt(该文件显然不会存在于 Playground 中)。包的*Open*函数os具有以下签名,

*func Open(名称字符串) (File, error)

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

如果函数或方法返回错误,那么按照惯例,它必须是函数返回的最后一个值。因此该Open函数返回error最后一个值。

**Go 中处理错误的惯用方法是将返回的错误与nil. nil 值表示没有发生错误,非 nil 值表示存在错误。**在我们的例子中,

我们检查错误是否不等于nil。如果不是nil,我们只需打印错误并从主函数返回。

运行该程序将打印

open /test.txt: No such file or directory  

完美😃。我们收到一条错误消息,指出该文件不存在。

错误类型表示

让我们更深入地研究一下内置error类型是如何定义的。error是具有以下定义的接口类型

type error interface {  Error() string
}

它包含一个带有签名的方法Error() string。任何实现此接口的类型都可以用作错误。此方法提供错误的描述。

当打印错误时,fmt.Println函数内部调用该Error() string 方法来获取错误的描述打印方式

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

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

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

open /test.txt: No such file or directory  

我们可以解析此错误消息并获取导致错误的文件的文件路径“/test.txt”,但这是一种肮脏的做法。在较新版本的 Go 中,错误描述可能随时更改,我们的代码将会崩溃。

有没有更好的方法来获取文件名🤔?答案是肯定的,这是可以做到的,并且 Go 标准库使用不同的方式来提供有关错误的更多信息。让我们一一看看。

1. 将错误转换为基础类型并从结构体字段中检索更多信息

如果你仔细阅读Open函数的文档,你会发现它返回一个类型为*PathError. PathError的错误,它是一个结构体类型,它在标准库中的实现如下:

type PathError struct {  Op   stringPath stringErr  error
}func (e *PathError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.Error() }  

如果您有兴趣知道上述源代码存在于哪里,可以在这里找到https://cs.opensource.google/go/go/+/refs/tags/go1.19:src/io/fs/fs .go;l=250

从上面的代码中,你可以明白,是通过声明方法*PathError来实现的。此方法将操作、路径和实际错误连接起来并返回。因此我们得到了错误消息,error interface``Error() string

open /test.txt: No such file or directory  

Pathstruct 字段 包含PathError导致错误的文件的路径。

我们可以使用errors包中的As函数将错误转换为其基础类型。该As函数的描述谈到了错误链。请暂时忽略它。我们将在单独的教程中了解错误链和包装的工作原理。
简单的描述As是,它尝试将错误转换为错误类型,并返回 true 或 false 指示转换是否成功。

一个程序会让事情变得清晰。让我们修改上面编写的程序并使用该As函数打印路径。

package mainimport (  "errors""fmt""os"
)func main() {  f, err := os.Open("test.txt")if err != nil {var pErr *os.PathErrorif errors.As(err, &pErr) {fmt.Println("Failed to open file at path", pErr.Path)return}fmt.Println("Generic error", err)return}fmt.Println(f.Name(), "opened successfully")
}

Run in playground

在上面的程序中,我们首先检查错误是否不在nil, 然后我们使用As的函数。 转换err*os.PathError.

如果转换成功,As将返回true

如果您想知道为什么pErr是指针,原因是错误接口是由指针实现的PathError,因此pErr是指针。下面的代码显示了*PathError错误接口的实现。

func (e *PathError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.Error() }  

As函数要求第二个参数是指向实现错误的类型的指针。因此我们通过了&perr

该程序输出,

Failed to open file at path test.txt  

如果底层错误不是*os.PathError 类型,则打印一般错误消息。

太棒了😃。我们已经成功地使用该As函数从错误中获取文件路径。

2. 使用方法检索更多信息

从错误中获取更多信息的第二种方法是找出基础类型并通过调用结构类型上的方法来获取更多信息。

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

标准库中的DNSError*结构*类型定义如下:

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

DNSError结构有两个方法Timeout() boolTemporary()它们返回一个布尔值,指示错误是由于超时还是临时错误。

让我们编写一个程序,将错误转换为*DNSError类型,并调用上述方法来确定错误是暂时的还是由于超时造成的。

package mainimport (  "errors""fmt""net"
)func main() {  addr, err := net.LookupHost("baidu12345.com")if err != nil {var dnsErr *net.DNSErrorif errors.As(err, &dnsErr) {if dnsErr.Timeout() {fmt.Println("operation timed out")return}if dnsErr.Temporary() {fmt.Println("temporary error")return}fmt.Println("Generic DNS error", err)return}fmt.Println("Generic error", err)return}fmt.Println(addr)
}

注意:DNS 查找在 Playground 中不起作用。请在您的本地计算机上运行该程序。

在上面的程序中,我们正在尝试获取无效域名的IP地址baidu123.com。我们通过使用As该函数并将其转换为DNSError 来 获取错误的基本值。然后我们分别在14和18行检查错误是由于超时还是暂时错误的。

在我们的例子中,错误既不是暂时的,也不是由于超时造成的,因此程序将打印:

Generic DNS error lookup baidu12345.com: no such host

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

3. 直接比较

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

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

ErrBadPattern在包中定义filepath为全局变量。

var ErrBadPattern = errors.New("syntax error in pattern")  

error.New() 用于创建一个新错误。我们将在下一个教程中详细讨论这一点。

当模式格式错误时,Glob 函数将返回ErrBadPattern 。

让我们编写一个小程序来检查此错误。

package mainimport (  "errors""fmt""path/filepath"
)func main() {  files, err := filepath.Glob("[")if err != nil {if errors.Is(err, filepath.ErrBadPattern) {fmt.Println("Bad pattern error:", err)return}fmt.Println("Generic error:", err)return}fmt.Println("matched files", files)
}

Run in playground

在上面的程序中,我们搜索格式[错误的模式文件。我们检查错误是否不为nil。为了获得有关错误的更多信息,我们直接使用Is函数将filepath.ErrBadPatterninline 进行比较。与 类似As,该Is函数在错误链上工作。我们将在下一个教程中了解更多相关内容。
出于本教程的目的,如果传递给该函数的两个错误相同,则Is可以将该函数视为true然后返回。

Is第 12 行返回 true 。因为错误是由于格式错误造成的。该程序将打印,

Bad pattern error: syntax error in pattern  

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

不要忽视错误

永远不要忽视错误。忽略错误会招致麻烦。让我重写示例,其中列出了与模式匹配的所有文件的名称,忽略错误。

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

Run in playground

从前面的例子中我们已经知道是无效的。我通过_使用空白标识符忽略了函数返回的错误。我只是打印匹配的文件。该程序将打印

matched files []  

由于我们忽略了该错误,因此输出看起来好像没有文件与该模式匹配,但实际上该模式本身格式错误。所以永远不要忽视错误。

本教程到此结束。


http://www.ppmy.cn/news/1201160.html

相关文章

仿mudou库one thread one loop式并发服务器

目录 1.实现目标 2.HTTP服务器 实现高性能服务器-Reactor模型 模块划分 SERVER模块: HTTP协议模块: 3.项目中的子功能 秒级定时任务实现 时间轮实现 正则库的简单使用 通⽤类型any类型的实现 4.SERVER服务器实现 日志宏的封装 缓冲区Buffer…

git使用全解析 | git的原理 配置 基础使用 分支 合并

文章目录 1 git初步了解1.1 git的安装1.2 git原理模型1.3 git基础配置1.4 git基础用法1 将文件加入暂存区2 查看当前的git仓库状态3 删除文件4 commit 将暂存区文件加入本地git版本仓库5 查看提交历史 更改 2 分支2.1 创建分支2.2 查看分支2.3 切换分支2.4 内容比较 3 合并 本文…

python opencv 实现对二值化后的某一像素值做修改和mask叠加

实现对二值化后的某一像素值做修改 使用OpenCV的findNonZero函数找到所有非零(也就是像素值为255)的像素,然后遍历这些像素并修改他们的值。示例代码: import cv2 import numpy as np # 加载并二值化图像 img cv2.imread(…

大厂真题:【模拟】阿里蚂蚁2023秋招-讨厌鬼的区间

题目描述与示例 题目描述 讨厌鬼有一个数x,他每次操作可以令x x 1或x x - 1 讨厌鬼还有两个区间[l1, r1]和[l2, r2],讨厌鬼想知道,令x同时满足以下条件的最小操作数是多少? l1 ≤ x ≤ r1,且x是2的倍数l2 ≤ x ≤ r2&#xf…

Pandas数据分析Pandas进阶在线闯关_头歌实践教学平台

Pandas数据分析进阶 第1关 Pandas 分组聚合第2关 Pandas 创建透视表和交叉表 第1关 Pandas 分组聚合 任务描述 本关任务:使用 Pandas 加载 drinks.csv 文件中的数据,根据数据信息求每个大洲红酒消耗量的最大值与最小值的差以及啤酒消耗量的和。 编程要求…

Linux文件系统目录结构

典型的Linux文件系统目录结构的列表 典型的Linux文件系统目录结构的列表。每个目录都有其特定的用途: /bin: 存放系统引导和修复所需的二进制可执行文件,如ls,cp,mv等命令。 /boot: 存放操作系统引导文件,例如内核和…

基于SSM的出租车管理系统

基于SSM的出租车管理系统的设计与实现~ 开发语言:Java数据库:MySQL技术:SpringSpringMVCMyBatis工具:IDEA/Ecilpse、Navicat、Maven 系统展示 登录界面 管理员界面 驾驶员界面 摘要 基于SSM(Spring、Spring MVC、My…

详解基于Android的Appium+Python自动化脚本编写

📢专注于分享软件测试干货内容,欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!📢交流讨论:欢迎加入我们一起学习!📢资源分享:耗时200小时精选的「软件测试」资…