golang异常处理panic recover error基本介绍

2022-12-30 11:17:57

背景

在 go 中没有 try catch 的异常处理语句,但是有 defer,panic,recovor,error 来控制程序执行流程

panic

When youpanicin Go, you’re freaking out, it’s not someone elses problem, it’s game over man.

当某函数 fun 出发 panic 异常,fun 后面代码停止运行,转而去运行 defer 代码(如果有 defer),再然后结束 fun 函数,并将当前处理权交给 fun 的调用函数,recover 之后函数正常往下运行。panic 返回值通过 recover 函数获取,如果 panic 函数没有被调用或者没有返回值,那么 recover 返回 nil

panic 函数声明

我们先看下 panic 函数声明如下:

func panic(v interface{})

panic 返回

panic 的返回值什么呢?下方 panic 形式通过 recover 函数接收都是 “abc”

panic("abc")
// 因为 Sprint 本身返回这个就是 string
panic(fmt.Sprint("abc"))

panic 触发

常见的引发 panic 的如:

// 数组下标越界
var bar = []int{1}
fmt.Println(bar[1])

// 访问未初始化的指针或 nil 指针
var bar *int
fmt.Println(*bar)

// 往 close 的 chan 发送数据
close(bar)
bar<-1

// 并发读写相同 map

// 对接口进行类型断言
var a interface{}
var b string = "abc"
a = &b
v := a.(string)

// 其他更多的 panic

recover

recover 函数本身是用来处理 panic,表示当前 goroutine 是否有 panic 产生,只有当 recover 函数写在 defer 中时候方能起到捕获 panic 异常的效果

recover 很像一个 try catch,一旦把异常捕获到了,之后的程序又是正常执行了

recover 函数声明

func recover() interface{}

panic & recover 处理异常

panic 和 recover 的一个小例子

func main() {
  defer func() {
    if r := recover(); r != nil {
      fmt.Println(r)
    }
  }()
  // panic 中也可以传递 error
  panic("出现了panic!")
  fmt.Println("这里执行了吗?")
}

最后执行的结果只有「出现了panic!」输出的是 defer 中的语句

recover 后程序正常执行

我们直接看一个例子观察它的打印结果是如何的。这个例子中 foo 中触发一个越界异常,然后 foo recover 之后把执行权交给 main

func foo(){
	defer func(){
		if err := recover(); err != nil {
			fmt.Println(err)
		}
	}()
	var sli = []int{1}
	// 下标越界异常触发
	fmt.Println(sli[1])
	fmt.Println("这里不执行")
}

func main(){
	foo()
	fmt.Println("这里要执行")
}

执行的结果是显示如下。第一行是 defer 中打印的错误信息,然后 foo 把执行权交接给 main,因为 foo 中 defer 里已经把异常处理了,然后 main 执行 foo 函数之后,打印语句正常执行

runtime error: index out of range [1] with length 1
这里要执行

error 基本用法

err != nil 处理错误

上方我们看到了 panic 处理异常的方式,这里我们再看看 error 处理错误的方式

func foo() error {
  // ...
}

func main() {
  // 异常处理
  if err := foo; err != nil {
    // ...
  }
}

新处理错误的方式

上面通过不断的判断 err!=nil 会很麻烦,原因我们可以试想一下,如果一个方法会返回 error,而我们需要多次调用它,这就意味着每调用一次,那么我们下面就要进行 err!=nil 来判定一次错误,这会非常麻烦,因为我们有什么方式可以优化它呢?

type foo struct {
	x Xxx
	e error
}

func (f *foo) bar() {
	if f.e != nil {
		return
	}
	f.e = foobar()
}

采用新的方式来判断错误,我们只需要把经常产生返回错误的方法 foobar() 封装起来让它不用总是返回 error,并且里头做一个判断,如果调用该固定返回 error 的方法之前错误不是 nil,则直接结束,如果错误是 nil,则结构体中 e 接续承接该固定返回 error 的方法,所以我们可以在调用好几轮这个 bar() 方法后,最后一步再去对 error 做判断

产生一个错误

// 返回值是 error 类型
errors.New("一个新错误")

自定义错误

因为 error 接口有一个 Error 方法,当我们实现这个方法时候,实际上也就是实现了这个接口,也就是说自己的这个 struct 也成为了 error 接口

type foo struct {
	// ...
}

func (f foo) Error() string {
	// ...
}

当实现了 error 接口之后打印就能看到错误信息了

error 实践用法

  • 当我们需要判断 error 是不是哪种 error 的时候我们可以使用 errors.Is() 函数,这个是在 go1.13 之后出现的很好判定 err 的用法
    err := io.EOF
    err = fmt.Errorf("xxxxxxx%w", io.EOF)
    // 下方 if 是 true
    if errors.Is(err, io.EOF) {
    	fmt.Println("Is函数可以用来明确此 error 是不是某种异常,虽然 err != io.EOF")
    }
    
  • 还有errors.As()用力啊判定 err 是不是某一种类型

panic 还是 error?

一般而言只用 error 错误处理,panic 是重大异常;error 是程序员可以预知的错误,panic 是程序员预料之外的异常

所以有一个原则就是我们写代码时候尽量使用 error 进行错误处理,尽量去避免 panic 处理,触发实在遇到了

  • 作者:abcnull
  • 原文链接:https://abcnull.blog.csdn.net/article/details/118088819
    更新时间:2022-12-30 11:17:57