Golang 错误处理|Go主题月

构造 error

在 go 语言中,有一个预约义的接口:error,该接口自带一个 Error() 方法,调用该方法会返回一个字符串。sql

type error interface {
  Error() string
}
复制代码

调用该方法,会返回当前错误的具体结果。通常有下面几种方式生成 error。markdown

  • errors.New()
  • fmt.Errorf()

errors.New()

调用 errors.New() 会返回一个 error 类型的结构体,该结构体内部会实现一个 Error() 方法, 调用该方法返回的结果为调用 errors.New() 方法时传入的内容。数据结构

import (
	"errors"
	"fmt"
)

func divide(a, b int) (error, int) {
	if b == 0 {
    // 被除数为0,则构造一个 error 结构体
		return errors.New("被除数不能为0"), 0
	}
	var result = a / b
	return nil, result
}

func main() {
	var err error // error 类型数据的初始值为 nil,相似于 js 中的 null
	var result int

	err, result = divide(1, 0)

  if err == nil {
    // 若是 err 为 nil,说明运行正常
    fmt.Println("计算结果", result)
  } else {
    // 若是 err 不为 nil,说明运行出错
    // 调用 error 结构体的 Error 方法,输出错误缘由
    fmt.Println("计算出错", err.Error())
  }
}
复制代码

能够看到,上面的代码中,因为调用 divide 除法方法时,因为传入的被除数为 0。通过判断,会抛出一个由 errors.New 构造的 error 类型的结构体。ide

咱们将调用 error.Error() 方法返回的结果输出到控制台,能够发现其返回的结果,就是传入 New 方法的值。函数

执行结果以下:ui

fmt.Errorf()

经过 fmt.Errorf() 方法构造的 error 结构体,与调用 errors.New() 方法的结果相似。不一样的是,fmt.Errorf() 方法会进行一次数据的格式化。spa

func divide(a, b int) (error, int) {
	if b == 0 {
    // 将参数进行一次格式化,格式化后的字符串放入 error 中
		return fmt.Errorf("数据 %d 不合法", b), 0
	}
	var result = a / b
	return nil, result
}

err, result := divide(1, 0)
fmt.Println("计算出错", err.Error())
复制代码

执行结果以下:3d

panic() 与 recover()

panic()

panic() 至关于主动中止程序运行,调用时 panic() 时,须要传入中断缘由。调用后,会在控制台输出中断缘由,以及中断时的调用堆栈。咱们能够改造一下以前的代码:日志

func divide(a, b int) (error, int) {
	if b == 0 {
    // 若是程序出错,直接中止运行
		panic("被除数不能为0")
	}
	var result = a / b
	return nil, result
}

func main() {
  err, result := divide(1, 0)
  fmt.Println("计算出错", err.Error())
}
复制代码

在运行到 panic() 处,程序直接中断,并在控制台打印出了中断缘由。code

panic() 能够理解为,js 程序中的 throw new Error() 的操做。那么,在 go 中有没有办法终止 panic() ,也就是相似于 try-catch 的操做,让程序回到正常的运行逻辑中呢?

recover()

在介绍 recover() 方法以前,还须要介绍一个 go 语言中的另外一个关键字:defer

defer 后的语句会在函数进行 return 操做以前调用,经常使用于资源释放错误捕获日志输出

func getData(table, sql) {
  defer 中断链接()
  db := 创建链接(table)
  data := db.select(sql)
  return data
}
复制代码

defer 后的语句会被存储在一个相似于栈的数据结构内,在函数结束的时候,被定义的语句按顺序出栈,越后面定义的语句越先被调用。

func divide(a, b int) int {
  defer fmt.Println("除数为", b)
  defer fmt.Println("被除数为", a)

  result := a / b
  fmt.Println("计算结果为", result)
	return result
}

divide(10, 2)
复制代码

上面的代码中,咱们在函数开始运行的时候,先经过 defer 定义了两个输出语句,先输出除数,后输出被除数

实际的运行结果是:

  • 先输出计算结果;
  • 而后输出被除数;
  • 最后输出除数;

这和前面提到的,经过 defer 定义的语句会在函数结束的时候,按照出栈的方式进行执行,先定义的后执行。defer 除了会在函数结束的时候执行,出现异常的的时候也会先走 defer 的逻辑,也就是说,咱们在调用了 panic() 方法后,程序中断过程当中,也会先将 defer 内的语句运行一遍。

这里咱们从新定义以前的 divide 函数,在执行以前加上一个 defer 语句,defer 后面为一个自执行函数,该函数内会调用 recover() 方法。

recover() 方法调用后,会捕获到当前的 panic() 抛出的异常,并进行返回,若是没有异常,则返回 nil

func divide(a, b int) int {
  // 中断以前,调用 defer 后定义的语句
	defer func() {
		if err := recover(); err != nil {
			fmt.Println("捕获错误", err)
		}
	}()

	if b == 0 {
    // 函数运行被中断
		panic("被除数不能为0")
		return 0
	}

	return a / b
}

result := divide(1, 0)
fmt.Println("计算结果", result)
复制代码

上面的代码运行后,咱们发现以前调用 panic() 中断的程序被恢复了,并且后面的计算结果也正常进行输出了。

这就有点相似于 try-catch 的逻辑了,只是 recover 须要放在 defer 关键词后的语句中,更像是 catchfinally 的结合。

相关文章
相关标签/搜索