脚本之家,脚本语言编程技术及教程分享平台!
分类导航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|

服务器之家 - 脚本之家 - Golang - Golang中的错误处理的示例详解

Golang中的错误处理的示例详解

2022-12-02 14:49liuyuede123 Golang

这篇文章主要为大家详细介绍了Golang中的错误处理的相关资料,文章中的示例代码讲解详细,对我们学习Golang有一定帮助,需要的可以参考一下

1、panic

当我们执行panic的时候会结束下面的流程:

?
1
2
3
4
5
6
7
8
9
package main
 
import "fmt"
 
func main() {
    fmt.Println("hello")
    panic("stop")
    fmt.Println("world")
}

输出:

go run 9.go 
hello
panic: stop

但是panic也是可以捕获的,我们可以使用defer和recover实现:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main
 
import "fmt"
 
func main() {
 
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("recover: ", r)
        }
    }()
 
    fmt.Println("hello")
    panic("stop")
    fmt.Println("world")
}

输出:

go run 9.go
hello
recover:  stop

那什么时候适合panic呢?在 Go 中,panic 用于表示真正的异常,例如程序错误。我们经常会在一些内置包里面看到panic的身影。

比如strings.Repeat重复返回一个由字符串 s 的计数副本组成的新字符串:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
func Repeat(s string, count int) string {
    if count == 0 {
        return ""
    }
 
    //
    if count < 0 {
        panic("strings: negative Repeat count")
    } else if len(s)*count/count != len(s) {
        panic("strings: Repeat count causes overflow")
    }
 
    ...
}

我们可以看到当重复的次数小于0或者重复count次之后s的长度溢出,程序会直接panic,而不是返回错误。这时因为strings包限制了error的使用,所以在程序错误时会直接panic。

还有一个例子是关于正则表达式的例子:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main
 
import (
    "fmt"
    "regexp"
)
 
func main() {
    pattern := "a[a-z]b*" // 1
    compile, err := regexp.Compile(pattern) // 2
    if err != nil { // 2
        fmt.Println("compile err: ", err)
        return
    }
  // 3
    allString := compile.FindAllString("acbcdadb", 3)
    fmt.Println(allString)
 
}
  • 编写一个正则表达式
  • 调用Compile,解析正则表达式,如果成功,返回用于匹配文本的 Regexp 对象。否则返回错误
  • 利用正则,在输入的字符串中,获取所有的匹配字符

可以看到如果上面正则解析失败是可以继续往下执行的,但是regexp包中还有另外一个方法MustCompile:

?
1
2
3
4
5
6
7
func MustCompile(str string) *Regexp {
    regexp, err := Compile(str)
    if err != nil {
        panic(`regexp: Compile(` + quote(str) + `): ` + err.Error())
    }
    return regexp
}

这个方法说明正则的解析是强依赖的,如果解析错误,直接panic结束程序。用户可以根据实际情况选择。

但是实际开发中我们还是要谨慎使用panic,因为它会使程序结束运行(除非我们调用defer recover)

2、包装错误

错误包装是将错误包装或者打包在一个包装容器中,这样的话我们就可以追溯到源错误。错误包装的主要作用就是:

  • 为错误添加上下文
  • 将错误标记为特定类型的错误

我们可以看一个访问数据库的例子:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package main
 
import (
    "fmt"
    "github.com/pkg/errors"
)
 
type Courseware struct {
    Id int64
    Code string
    Name string
}
 
func getCourseware(id int64) (*Courseware, error) {
    courseware, err := getFromDB(id)
    if err != nil {
        return nil, errors.Wrap(err, "六月的想访问这个课件") // 2
    }
    return courseware, nil
}
 
func getFromDB(id int64) (*Courseware, error) {
    return nil, errors.New("permission denied") // 1
}
 
func main() {
    _, err := getCourseware(11)
    if err != nil {
        fmt.Println(err)
    }
}
  • 访问数据库时我们返回了原始的错误信息
  • 到上层我们添加了一些自定义的上下文信息

输出:

go run 9.go
六月的想访问这个课件: permission denied

当然我们也可以将错误包装成我们自定义类型的错误,我们稍微修改下上面的例子:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package main
 
import (
    "fmt"
    "github.com/pkg/errors"
)
 
type Courseware struct {
    Id int64
    Code string
    Name string
}
 
// 1
type ForbiddenError struct {
    Err error
}
 
// 2
func (e *ForbiddenError) Error() string {
    return "Forbidden: " + e.Err.Error()
}
 
func getCourseware(id int64) (*Courseware, error) {
    courseware, err := getFromDB(id)
    if err != nil {
        return nil, &ForbiddenError{err} // 4
    }
    return courseware, nil
}
 
func getFromDB(id int64) (*Courseware, error) {
    return nil, errors.New("permission denied") // 3
}
 
func main() {
    _, err := getCourseware(11)
    if err != nil {
        fmt.Println(err)
    }
}
  • 首先我们自定义了ForbiddenError的错误类型
  • 我们实现了error接口
  • 访问数据库抛出原始错误
  • 上层返回ForbiddenError类型的错误

输出:

go run 9.go
Forbidden: permission denied

当然我们也可以不用创建自定义错误的类型,去包装错误添加上下文:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package main
 
import (
    "fmt"
    "github.com/pkg/errors"
)
 
type Courseware struct {
    Id int64
    Code string
    Name string
}
 
 
func getCourseware(id int64) (*Courseware, error) {
    courseware, err := getFromDB(id)
    if err != nil {
        return nil, fmt.Errorf("another wrap err: %w", err) // 1
    }
    return courseware, nil
}
 
func getFromDB(id int64) (*Courseware, error) {
    return nil, errors.New("permission denied")
}
 
func main() {
    _, err := getCourseware(11)
    if err != nil {
        fmt.Println(err)
    }
}

使用%w包装错误

使用这的好处是我们可以追溯到源错误,从而方便我们做一些特殊的处理。

还有一种方式是使用:

?
1
return nil, fmt.Errorf("another wrap err: %v", err)

%v的方式不会包装错误,所以无法追溯到源错误,但往往有时候我们会选择这种方式,而不用%w的方式。%w的方式虽然能包装源错误,但往往我们会通过源错误去做一些处理,假如源错误被修改,那包装这个源错误的相关错误都需要做响应变化。

3、错误类型判断

我们扩展一下上面查询课件的例子。现在我们有这样的判断,如果传进来的id不合法我们返回400错误,如果查询数据库报错我们返回500错误,我们可以像下面这样写:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package main
 
import (
    "fmt"
    "github.com/pkg/errors"
)
 
type Courseware struct {
    Id int64
    Code string
    Name string
}
 
type ForbiddenError struct {
    Err error
}
 
func (e *ForbiddenError) Error() string {
    return "Forbidden: " + e.Err.Error()
}
 
func getCourseware(id int64) (*Courseware, error) {
    if id <= 0 {
        return nil, fmt.Errorf("invalid id: %d", id)
    }
    courseware, err := getFromDB(id)
    if err != nil {
        return nil, &ForbiddenError{err}
    }
    return courseware, nil
}
 
func getFromDB(id int64) (*Courseware, error) {
    return nil, errors.New("permission denied")
}
 
func main() {
    _, err := getCourseware(500) // 我们可以修改这里的id看下打印的结构
    if err != nil {
        switch err := err.(type) {
        case *ForbiddenError:
            fmt.Println("500 err: ", err)
        default:
            fmt.Println("400 err: ", err)
        }
    }
}

输出:

go run 9.go
500 err:  Forbidden: permission denied

这样看起来好像也没什么问题,现在我们稍微修改下代码,把上面ForbiddenError包装一下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package main
 
import (
    "fmt"
    "github.com/pkg/errors"
)
 
type Courseware struct {
    Id int64
    Code string
    Name string
}
 
type ForbiddenError struct {
    Err error
}
 
func (e *ForbiddenError) Error() string {
    return "Forbidden: " + e.Err.Error()
}
 
func getCourseware(id int64) (*Courseware, error) {
    if id <= 0 {
        return nil, fmt.Errorf("invalid id: %d", id)
    }
    courseware, err := getFromDB(id)
    if err != nil {
        return nil, fmt.Errorf("wrap err: %w", &ForbiddenError{err}) // 这里包装了一层错误
    }
    return courseware, nil
}
 
func getFromDB(id int64) (*Courseware, error) {
    return nil, errors.New("permission denied")
}
 
func main() {
    _, err := getCourseware(500)
    if err != nil {
        switch err := err.(type) {
        case *ForbiddenError:
            fmt.Println("500 err: ", err)
        default:
            fmt.Println("400 err: ", err)
        }
    }
}

输出:

go run 9.go
400 err:  wrap err: Forbidden: permission denied

可以看到我们的Forbidden错误进到了400里面,这并不是我们想要的结果。之所以会这样,是因为在ForbiddenError的外面又包装了一层Error错误,使用类型断言的时候判断出来的是Error错误,所以进到了400分支。

这里我们可以使用errors.As方法,它会递归调用Unwrap方法,找到错误链中第一个与target匹配的方法:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package main
 
import (
    "fmt"
    "github.com/pkg/errors"
)
 
type Courseware struct {
    Id int64
    Code string
    Name string
}
 
type ForbiddenError struct {
    Err error
}
 
func (e *ForbiddenError) Error() string {
    return "Forbidden: " + e.Err.Error()
}
 
func getCourseware(id int64) (*Courseware, error) {
    if id <= 0 {
        return nil, fmt.Errorf("invalid id: %d", id)
    }
    courseware, err := getFromDB(id)
    if err != nil {
        return nil, fmt.Errorf("wrap err: %w", &ForbiddenError{err})
    }
    return courseware, nil
}
 
func getFromDB(id int64) (*Courseware, error) {
    return nil, errors.New("permission denied")
}
 
func main() {
    _, err := getCourseware(500)
    if err != nil {
        var f *ForbiddenError // 这里实现了*ForbiddenError接口,不然会panic
        if errors.As(err, &f) { // 找到匹配的错误
            fmt.Println("500 err: ", err)
        } else {
            fmt.Println("400 err: ", err)
        }
    }
}

输出:

go run 9.go
500 err:  wrap err: Forbidden: permission denied

4、错误值判断

在代码中或者mysql库或者io库中我们经常会看到这样的全局错误:

?
1
var ErrCourseware = errors.New("courseware")

这种错误我们称之为哨兵错误。一般数据库没查到ErrNoRows或者io读到了EOF错误,这些特定的错误可以帮助我们做一些特殊的处理。

一般我们会直接用==号判断错误值,但是就像上面的如果错误被包装哪我们就不好去判断了。好在errors包中提供了errors.Is方法,通过递归调用Unwrap判断错误链中是否与目标错误相匹配的错误值:

?
1
2
3
4
5
6
7
if err != nil {
    if errors.Is(err, ErrCourseware) {
        // ...
    } else {
        // ...
    }
}

到此这篇关于Golang中的错误处理的示例详解的文章就介绍到这了,更多相关Golang错误处理内容请搜索服务器之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持服务器之家!

原文链接:https://www.cnblogs.com/liuyuede123/p/16850908.html

延伸 · 阅读

精彩推荐
  • Golanggolang等待触发事件的实例

    golang等待触发事件的实例

    这篇文章主要介绍了golang等待触发事件的实例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    思维的深度4182021-03-18
  • Golanggolang 手写贪吃蛇示例实现

    golang 手写贪吃蛇示例实现

    这篇文章主要为大家介绍了golang 手写贪吃蛇示例实现,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪...

    黑胡子Z5272022-07-21
  • GolangGo语言中常量定义方法实例分析

    Go语言中常量定义方法实例分析

    这篇文章主要介绍了Go语言中常量定义方法,以实例形式分析了Go语言中常量的定义及使用技巧,具有一定参考借鉴价值,需要的朋友可以参考下 ...

    脚本之家2432020-04-16
  • Golanggolang 函数返回chan类型的操作

    golang 函数返回chan类型的操作

    这篇文章主要介绍了golang 函数返回chan类型的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    CodeMechine4972021-06-07
  • GolangGo 语言实现安全计数的若干种方法

    Go 语言实现安全计数的若干种方法

    我正研究共享计数器的简单经典实现,实现方式使用的是 C++ 中的互斥锁,这时,我非常想知道还有哪些线程安全的实现方式。我通常使用 Go 来满足自己的...

    Golang来啦6772021-07-26
  • GolangGO 实现高并发高可用分布式系统:Log微服务的实现

    GO 实现高并发高可用分布式系统:Log微服务的实现

    在大数据时代,具备高并发,高可用,理解微服务系统设计的人员需求很大,如果你想从事后台开发,在JD的描述中最常见的要求就是有所谓的“高并发”...

    Coding迪斯尼4702022-01-10
  • Golang详解Go语言中单链表的使用

    详解Go语言中单链表的使用

    链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。本文将通过实例为大家详解Go语言中单链表的常见用法,感兴趣的可...

    Hann Yang6532022-11-10
  • Golanggolang简单获取上传文件大小的实现代码

    golang简单获取上传文件大小的实现代码

    这篇文章主要介绍了golang简单获取上传文件大小的方法,涉及Go语言文件传输及文件属性操作的相关技巧,需要的朋友可以参考下 ...

    dotcoo10692020-04-29