请选择 进入手机版 | 继续访问电脑版

[Golang] golang 语言中错误处理机制

[复制链接]
查看182 | 回复45 | 2021-9-15 03:10:01 | 显示全部楼层 |阅读模式

与其他主流语言如 Javascript、Java 和 Python 相比,Golang 的错误处理方式大概 和这些你熟悉 的语言有所不同。以是 才有了这个想法根大家聊一聊 golang 的错误处理方式,以及现实 开辟 中应该怎样 对错误举行 处理。由于 分享面临 Golang有一个基本的相识 developers, 以是 一些简单地方就不做赘述了。

怎样 定义错误

在 golang 语言中,无论是在范例 检查还是编译过程中,都是将错误看做值来对待,和 string 或者 integer 这些范例 值并不差别。声明一个 string 范例 变量和声明一个 error 范例 变量是没什么区别的。

你可以定义接口作为 error 的范例 ,有关 error 可以或许 提供什么样信息都是由本身 决定的,这是 error 在 golang 作为值的好处,不过如许 做也天然 有其坏处,有关 error 定义好坏就全由其定义开辟 职员 所决定,也就是有关 error 融入过多人为的主观因素。

  1. package main
  2. import (
  3. "fmt"
  4. "io/ioutil"
  5. )
  6. func main(){
  7. dir, err := ioutil.TempDir("","temp")
  8. if err != nil{
  9. fmt.Errorf("failed to create temp dir: %v",err)
  10. }
  11. }
复制代码

错误在语言中的重点地位

在 Go 语言中错误处理计划 不停 大家喜好 讨论的内容,错误处理是该语言的核心,但该语言并没有规定怎样 处理错误。社区已经为改进和规范错误处理做出了积极 ,但很多 人忽略了错误在我们应用程序范畴 中的核心地位。也就是说,错误与客户和订单范例 一样紧张 。

Golang中的错误

错误表示在应用程序中发生了不必要 的环境 。比方说,想创建一个临时 目次 ,在那边 可以为应用程序存储一些文件,但这个目次 的创建失败了。这是一个不渴望 的环境 ,就可以用错误来表示。

通过创建自定义错误可以将更丰富错误信息传递给调用者。个返回值返回将错误交给调用函数人来处理错误。Golang 本身答应 函数具有多个返回值,以是 通常把错误作为函数末了 一个参数返回给调用者来处理。

errors 是 I/O

  • 偶然 间 开辟 职员 是 error 的生产者(写 error)
  • 偶然 间 开辟 职员 又是 error 的斲丧 者(读 error)

也就是我们开辟 程序一部分工作是读取和写入 error

errors 的上下文
什么是 error 的上下文呢? 怎样 定义 error 必要 思量 一些因素,比方 在不同程序我们定义 error 和处理 error 方式也不仅雷同

  1. CLI 工具
  2. 长时间运行的体系

而且我们必要 思量 使用 程序的人群,他们是什么方式来使用 体系 ,这些因素都是我们计划 也好定义错误信息要思量 的因素。

错误的范例

就错误核心,那么错误大概 是我们预料之中的错误,错误也大概 是我们没有思量 到,比方 无效内存,数组越界,也就是单靠代码自身临时 是办理 不了的错误 ,如许 的毛病 每每 让代码恐慌,以是 Panic。通常如许 错误对于程序是灾难 性的失败,无法修复的。

自定义错误

如前所述,错误使用 内置的错误接口范例 来表示,其定义如下。

  1. type error interface {
  2. Error() string
  3. }
复制代码

下面举了 2 例子来定义 error ,分别定义两个 struct 都实现了 Error() 接口即可

  1. type SyntaxError struct {
  2. Line int
  3. Col int
  4. }
  5. func (e *SyntaxError) Error() string {
  6. return fmt.Sprintf("%d:%d: syntax error", e.Line, e.Col)
  7. }
复制代码
  1. type InternalError struct {
  2. Path string
  3. }
  4. func (e *InternalError) Error() string {
  5. return fmt.Sprintf("parse %v: internal error", e.Path)
  6. }
复制代码

该接口包含一个方法 Error() ,以字符串情势 返回错误信息。每一个实现了错误接口的范例 都可以作为一个错误使用 。当使用 fmt.Println 等方法打印错误时,Golang 会自动 调用 Error() 方法。

在 Golang 中,有多种创建自定义错误信息的方法,每一种都有本身 的长处 和缺点。

基于字符串的错误

基于字符串的错误可以用 Golang 中两个开箱即用方法来自定义错误,实用 哪些仅返回形貌 错误信息的相对来说比较简单的错误。

  1. err := errors.New("math: divided by zero")
复制代码

将错误信息传入到 errors.New() 方法可以用来新建一个错误

  1. err2 := fmt.Errorf("math: %g cannot be divided by zero", x)
复制代码

fmt.Errorf 通过字符串格式方式,可以将错误信息包含你错误信息中。也就是为错误信息添加了一些格式化的功能。

自定义数据布局 的错误

可以通过在你的布局 上实现 Error 接口中定义的 Error() 函数来创建自定义的错误范例 。下面是一个例子。

Defer, panic 和 recover

Go 并不像很多 其他编程语言(包括 Java 和 Javascript )那样有非常 ,但有一个雷同 的机制,即 "Defer, panic 和 recover"。然而,panic 和 recover 的使用 环境 与其他编程语言中的非常 非常不同,由于 代码本身无法应对时间 和不可恢复的环境 下使用 。

Defer

有点雷同 析构函数,在函数实验 完毕后做一些资源开释 等收尾工作,好处着实 行 和其在代码中位置并没有关系,以是 可以将其写在你读写资源语句后面,以免随后忘记做一些资源开释 的工作。关于 defer 输出也是口试 时,口试 官喜好 问的一个题目 。

  1. package main
  2. import(
  3. "fmt"
  4. "os"
  5. )
  6. func main(){
  7. f := createFile("tmp/machinelearning.txt")
  8. defer closeFile(f)
  9. writeFile(f)
  10. }
  11. func createFile(p string) *os.File {
  12. fmt.Println("creating")
  13. f, err := os.Create(p)
  14. if err != nil{
  15. panic(err)
  16. }
  17. return f
  18. }
  19. func closeFile(f *os.File){
  20. fmt.Println("closing")
  21. err := f.Close()
  22. if err != nil{
  23. fmt.Fprintf(os.Stderr, "error:%v\n",err)
  24. os.Exit(1)
  25. }
  26. }
  27. func writeFile(f *os.File){
  28. fmt.Println("writing")
  29. fmt.Fprintln(f,"machine leanring")
  30. }
复制代码

defer 语句会将函数推入到一个栈布局 中。同时栈布局 中的函数会在 return 语句实验 后被调用。

  1. package main
  2. import "fmt"
  3. func main(){
  4. // defer fmt.Println("word")
  5. // fmt.Println("hello")
  6. fmt.Println("hello")
  7. for i := 0; i <=3; i++ {
  8. defer fmt.Println(i)
  9. }
  10. fmt.Println("world")
  11. }
复制代码
  1. hello
  2. world
  3. 3
  4. 2
  5. 1
  6. 0
复制代码

可以通过在你的布局 上实现 Error 接口中定义的 Error() 函数来实现自定义错误范例 ,下面是一个例子。

Panic

panic 语句向 Golang 发出信号,这时通常是代码无法办理 当前的题目 ,以是 制止 代码的正常实验 流程。一旦调用了 panic,全部 的耽误 函数都会被实验 ,并且程序会崩溃,其日记 信息包括 panic 值(通常是错误信息)和堆栈跟踪。

举个例子,当一个数字被除以0时,Golang会出现 panic。

  1. package main
  2. import "fmt"
  3. func main(){
  4. divide(5)
  5. }
  6. func divide(x int){
  7. fmt.Printf("divide(%d)\n",x+0/x)
  8. divide(x-1)
  9. }
复制代码
  1. divide(5)
  2. divide(4)
  3. divide(3)
  4. divide(2)
  5. divide(1)
  6. panic: runtime error: integer divide by zero
  7. goroutine 1 [running]:
  8. main.divide(0x0)
  9. /Users/zidea2020/Desktop/mysite/go_tut/main.go:10 +0xdb
  10. main.divide(0x1)
  11. /Users/zidea2020/Desktop/mysite/go_tut/main.go:11 +0xcc
  12. main.divide(0x2)
  13. /Users/zidea2020/Desktop/mysite/go_tut/main.go:11 +0xcc
  14. main.divide(0x3)
  15. /Users/zidea2020/Desktop/mysite/go_tut/main.go:11 +0xcc
  16. main.divide(0x4)
  17. /Users/zidea2020/Desktop/mysite/go_tut/main.go:11 +0xcc
  18. main.divide(0x5)
  19. /Users/zidea2020/Desktop/mysite/go_tut/main.go:11 +0xcc
  20. main.main()
  21. /Users/zidea2020/Desktop/mysite/go_tut/main.go:6 +0x2a
  22. exit status 2
复制代码

Recover

Go语言提供了recover内置函数,前面提到,一旦panic,逻辑就会走到defer那,那我们就在defer那等着,调用recover函数将会捕获到当前的panic,被捕获到的panic就不会向上传递了。然后,恢复将竣事 当前的 Panic 状态,并返回 Panic 的错误值。

  1. package main
  2. import "fmt"
  3. func main(){
  4. accessSlice([]int{1,2,5,6,7,8}, 0)
  5. }
  6. func accessSlice(slice []int, index int) {
  7. defer func() {
  8. if p := recover(); p != nil {
  9. fmt.Printf("internal error: %v", p)
  10. }
  11. }()
  12. fmt.Printf("item %d, value %d \n", index, slice[index])
  13. defer fmt.Printf("defer %d \n", index)
  14. accessSlice(slice, index+1)
  15. }
复制代码

包装错误

Golang 也答应 对错误举行 包裹,通过错误嵌套,在原有错误信息上添加一个额外信息帮助调用者对题目 判定 以及后续应该怎样 处理信息。以通过使用 %w 标志和 fmt.Errorf 函数来对原有的错误举行 保存提供一些特定的信息,如下例所示。

  1. package main
  2. import (
  3. "errors"
  4. "fmt"
  5. "os"
  6. )
  7. func main() {
  8. err := openFile("non-existing")
  9. if err != nil {
  10. fmt.Printf("error running program: %s \n", err.Error())
  11. }
  12. }
  13. func openFile(filename string) error {
  14. if _, err := os.Open(filename); err != nil {
  15. return fmt.Errorf("error opening %s: %w", filename, err)
  16. }
  17. return nil
  18. }
复制代码

上面已经通过代码演示怎样 包装一个错误,程序会打印输出使用 fmt.Errorf 添加文件名的包装过的错误,也打印了传递给 %w 标志的原有错误信息。这里再补充一个 Golang 还提供的功能,通过使用 error.Unwrap 来还原错误信息,从而获得原有的错误信息。

  1. package main
  2. import (
  3. "errors"
  4. "fmt"
  5. "os"
  6. )
  7. func main() {
  8. err := openFile("non-existing")
  9. if err != nil {
  10. fmt.Printf("error running program: %s \n", err.Error())
  11. // Unwrap error
  12. unwrappedErr := errors.Unwrap(err)
  13. fmt.Printf("unwrapped error: %v \n", unwrappedErr)
  14. }
  15. }
  16. func openFile(filename string) error {
  17. if _, err := os.Open(filename); err != nil {
  18. return fmt.Errorf("error opening %s: %w", filename, err)
  19. }
  20. return nil
  21. }
复制代码

错误的范例 转换

偶然 间 必要 在不同的错误范例 之间举行 转换,有环境 必要 通过范例 转换来为错误添加信息,或者换一种表达方式,。 errors.As 函数提供了一个简单而安全的方法,通过探求 错误链中匹配错误范例 举行 转化输出。假如 没有找到匹配的,该函数返回 false 。

  1. package main
  2. import (
  3. "errors"
  4. "fmt"
  5. "io/fs"
  6. "os"
  7. )
  8. func main(){
  9. // Casting error
  10. if _, err := os.Open("non-existing"); err != nil {
  11. var pathError *os.PathError
  12. if errors.As(err, &pathError) {
  13. fmt.Println("Failed at path:", pathError.Path)
  14. } else {
  15. fmt.Println(err)
  16. }
  17. }
  18. }
复制代码

在这里,试图将通用错误范例 转换为 os.PathError ,如许 就可以访问该特定的错误信息,这些信息保存在布局 体中的 Path 属性上。

错误范例 检查

Golang 提供了 errors.Is 函数来用于检查错误范例 是否为指定的错误范例 ,该函数返回一个布尔值值来表示是否为指定错误范例 。

  1. package main
  2. import (
  3. "errors"
  4. "fmt"
  5. "io/fs"
  6. "os"
  7. )
  8. func main(){
  9. // Check if error is a specific type
  10. if _, err := os.Open("non-existing"); err != nil {
  11. if errors.Is(err, fs.ErrNotExist) {
  12. fmt.Println("file does not exist")
  13. } else {
  14. fmt.Println(err)
  15. }
  16. }
  17. }
复制代码

到此这篇关于golang 语言中错误处理机制的文章就先容 到这了,更多干系 golang 错误处理内容请搜刮 脚本之家从前 的文章或继续欣赏 下面的干系 文章盼望 大家以后多多支持脚本之家!


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

avatar hknuhp852859 | 2021-9-15 05:31:14 | 显示全部楼层
好帖子!
回复

使用道具 举报

avatar 成圣 | 2021-9-20 21:08:54 | 显示全部楼层
很经典,收藏了!
回复

使用道具 举报

avatar 123457287 | 2021-9-21 14:21:35 | 显示全部楼层
admin楼主好聪明啊!
回复

使用道具 举报

好好学习admin楼主的帖子!
回复

使用道具 举报

avatar 贺长云 | 2021-10-4 10:09:13 | 显示全部楼层
admin楼主说的我也略懂!
回复

使用道具 举报

avatar 没有昵称513 | 2021-10-5 20:14:55 | 显示全部楼层
论坛人气好旺!
回复

使用道具 举报

avatar 阿源285 | 2021-10-6 04:12:14 | 显示全部楼层
admin楼主练了葵花宝典吧?
回复

使用道具 举报

avatar 123457735 | 2021-10-6 05:35:39 | 显示全部楼层
求加金币!
回复

使用道具 举报

avatar 念佳泽 | 2021-10-7 03:36:44 | 显示全部楼层
每天顶顶贴,一身轻松啊!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则