文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

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

2024-04-02 19:55

关注

1、panic

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

package main

import "fmt"

func main() {
	fmt.Println("hello")
	panic("stop")
	fmt.Println("world")
}

输出:

go run 9.go 
hello
panic: stop

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

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 的计数副本组成的新字符串:

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。

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

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)

}

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

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、包装错误

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

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

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

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

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

输出:

go run 9.go
Forbidden: permission denied

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

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包装错误

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

还有一种方式是使用:

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

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

3、错误类型判断

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

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包装一下:

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匹配的方法:

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库中我们经常会看到这样的全局错误:

var ErrCourseware = errors.New("courseware")

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

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

if err != nil {
    if errors.Is(err, ErrCourseware) {
        // ...
    } else {
        // ...
    }
}

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

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯