异常处理

error接口

Go语言引入了一个关于错误处理的标准模式 它是Go语言内建的接口类型 它的定义如下

type error interface {
  Error() string
}

Go语言的标准库代码包errors为用户提供了以下方法

package errors
type errorString struct {
text string
}

func New(text string) error {
return &errorString{text}
}

func (e *errorString) Error() string {
return e.text
}

另一个可以生成 error 类型值的方法是调用 fmt 包中的 Errorf 函数:

package fmt
import "errors"
func Errorf(format string, args ...interface{}) error {
return errors.New(Sprintf(format, args...))
}

下面是代码示例

import (
"errors"
"fmt"
)
func main() {
var err1 error = errors.New("a normal err1")
fmt.Println(err1) //a normal err1
var err2 error = fmt.Errorf("%s", "a normal err2")
fmt.Println(err2) //a normal err2
}

一般来说 错误处理位于函数的最后 下面是代码示例

import (
"errors"
"fmt"
)

func Divide(a, b float64) (result float64, err error) {
if b == 0 {
result = 0.0
err = errors.New("runtime error: divide by zero")
return
}
result = a / b
err = nil
return
}
func main() {
r, err := Divide(10.0, 0)
if err != nil {
fmt.Println(err) //错误处理 runtime error: divide by zero
} else {
fmt.Println(r) // 使用返回值
}
}

panic

通常情况下 我们报错时可以返回一个error值

但是如果我们遇到了不可恢复的异常 比如说数组越界访问 空指针引用等

此时就会引发panic异常


一般而言 当当panic异常发生的时候 该程序会立刻停止运行 并且立即执行在该goroutine(我们这里暂时理解为线程)下的defer的函数

随后 程序崩溃并输出日志信息 日志信息包括panic value和函数调用的堆栈跟踪信息

此外需要注意的是 并不是所有panic异常都来自运行时 我们主动调用panic函数也会引起panic异常

panic函数可以接受任何值作为参数

func panic(v interface{})

下面是代码和结果演示

package main

import "fmt"

func testA() {
	fmt.Println("testA ~~~")
}

func testB() {
	panic("panic error ~~~")
}

func testC() {
	fmt.Println("testC ~~~")
}

func main() {
	testA()
	testB()
	testC()
}

我们发现前面的函数顺序执行完毕之后到panic异常

当然如果我们自己主动写点bug比如说数组越界 除0错误 效果也是一样的

recover

程序运行时 只要引发了panic异常就会引发程序崩溃 这是我们怎么都不愿意看到的

因此Go语言给我们提供了一种专门拦截运行时异常的内建函数 – recover

它可以使程序从panic状态恢复并获得流程控制权

注意:!!! recover只有在defer调用的函数中有效

如果说调用了内置函数decover并且定义该defer语句的函数发生了panic异常 recover会从panic中恢复 并且返回panic和vlaue 导致panic异常的函数不会正常运行 但会正常返回

但是在未发生panic时调用recover recover会返回nil

代码演示如下

package main

import "fmt"

func testA() {
	fmt.Println("testA ~~~")
}

func testB() (err error) {

	defer func() {
		if x := recover(); x != nil {
			// 此时panic和value被添加到x中
			err = fmt.Errorf("internal error: %v", x)
		}
	}()
	panic("panic error ~~~")

	return err
}

func testC() {
	fmt.Println("testC ~~~")
}

func main() {
	testA()
	err := testB()
	fmt.Println(err)
	testC()
}

我们依旧是在test2中主动调用了panic异常

只不过我们在前面延时调用了一个匿名函数

	defer func() {
		if x := recover(); x != nil {
			// 此时panic和value被添加到x中
			err = fmt.Errorf("internal error: %v", x)
		}
	}()

在这个匿名函数中我们调用了recover函数 并且接受了err

最终我们可以发现我们的程序正常运行下去了

【Hello Go】Go语言异常处理-LMLPHP

延时调用错误问题

如果我们在延时调用中也引发了panic异常 那么该panic异常可以被后续延时调用的recover捕获

代码和结果演示如下

func test() {
	defer func() {
		fmt.Println(recover())
	}()

	defer func() {
		panic("test2")
	}()

	panic("test1")
}

func main() {
	test()
}

此时我们的recover捕获的错误就不是test1 而是 test2

11-20 08:01