13.Go 异常

1、宕机

Go语言的类型系统会在编译时捕获很多错误,但有些错误只能在运行时检查,如数组访问越界、空指针引用等,这些运行时错误会引起宕机。

一般而言,当宕机发生时,程序会中断运行,并立即执行在该goroutine(线程)中被延迟的函数(defer机制),随后,程序崩溃并输出日志信息。日志信息包括panic value和函数调用的堆栈跟踪信息,panicvalue通常是某种错误信息。

引发宕机有如下两种情况:

  • 程序主动调用panic()函数。
  • 程序产生运行时错误,由运行时检测并抛出。

发生panic后,程序会从调用panic的函数位置或发生panic的地方立即返回,逐层向上执行函数的defer语句,然后逐层打印函数调用堆栈,直到被recover捕获或运行到最外层函数而退出。

panic的参数是一个空接口类型interface{},所以,任意类型的变量都可以传递给panic。调用panic的方法非常简单,即panic (xxx)。

panic不但可以在函数正常流程中抛出,在defer逻辑中也可以再次调用panic或抛出panic。defer中的panic能够被后续执行的defer捕获。

Go语言可以在程序中手动触发宕机,让程序崩溃,这样开发者可以及时发现错误,同时减少可能的损失。

Go语言程序在宕机时,会将堆栈和goroutine信息输出到控制台,所以,宕机也可以方便地确定发生错误的位置。

func main() {
    panic("crash")
}

当panic()触发的宕机发生时,panic()后面的代码将不会被运行,但是在panic()函数前面已经运行过的defer语句依然会在宕机发生时发生作用:

func main() {
	defer fmt.Println("宕机时要做的事情1...")
	defer fmt.Println("宕机时要做的事情2...")
	panic("crash")
}
2、宕机恢复

无论代码运行错误是由Runtime层抛出的panic崩溃,还是主动触发的panic崩溃,都可以配合defer和recover实现错误的捕捉和恢复,让代码发生崩溃后允许继续运行。

recover()用来捕获panic,阻止panic继续向上传递。recover()函数可以和defer语句一起使用,但recover()函数只有在defer后面的函数体内被直接调用才能捕获panic终止异常,否则会返回nil,异常继续向外传递。

可以有连续多个panic被抛出,连续多个被抛出的场景只能出现在延迟调用中。虽然有多个panic被抛出,但是只有最后一次的panic才能被捕获:

func main() {
	defer func() {
		if err := recover(); err != nil {
			fmt.Println(err)
		}
	}()

	// 只有最后一次的panic调用能够被捕获
	defer func() {
		panic("first defer panic")
	}()

	defer func() {
		panic("second defer panic")
	}()
	panic("main body panic")
}

panic和recover的关系如下:

  • 有panic没recover,程序宕机。
  • 有panic也有recover,程序不会宕机,执行完对应的defer语句后,从宕机点退出当前函数后继续执行。
3、错误与处理

Go语言的错误处理过程如下:

  • 一个可能造成错误的函数,需要返回值中返回一个错误接口,如果调用是成功的,错误接口将返回nil,否则返回错误。
  • 在函数调用后需要检查错误,如果发生错误,则进行必要的错误处理。
a. 错误接口

error是Go语言系统声明的接口类型,语法格式:type error interface {Error() string}

所有符合Error()string格式的方法,都能实现错误接口,Error()方法返回错误的具体描述,使用者可以通过该字符串知道发生了什么错误。

Go语言内置错误接口类型error。任何类型只要实现Error() string方法,都可以传递error接口类型变量。Go语言典型的错误处理方式是将error作为函数最后一个返回值。在调用函数时,通过检测其返回的error值是否为nil来进行错误处理。

b. 自定义错误

返回错误前,需要定义会产生哪些可能的错误,在Go语言中,使用errors包进行错误的定义,格式如下:

var err = errors.New("this is an error")

错误字符串由于相对固定,一般在包作用域声明,应尽量减少在使用时直接使用errors.New返回。

errors 包:Go语言的errors中对New的定义非常简单

// 创建错误对象
func New(text string) error {
	return &errorString{text}
}

// 错误字符串
type errorString struct {
	s string
}

// 返回发生何种错误
func (e *errorString) Error() string {
	return e.s
}
  • 第2行:将errorString结构体实例化,并赋值错误描述的成员。
  • 第7行:声明errorString结构体,拥有一个成员,描述错误内容。
  • 第12行:实现error接口的Error()方法,该方法返回成员中的错误描述。

在代码中使用错误定义------例如:定义一个除法函数,当除数为0时,返回一个预定义的除数为0的错误。

// 定义除数为0的错误
var errDivisionByZero = errors.New("division by zero")

func div(dividend, divisor int) (int, error) {
	// 判断除数为0的情况并返回
	if divisor == 0 {
		return 0, errDivisionByZero
	}
	// 正常计算,返回空错误
	return dividend / divisor, nil
}

func main() {
	fmt.Println(div(1, 0))
}
c. 错误和异常

错误是指发生非期望的已知行为,这里的已知是指错误的类型是预料并定义好的。

异常是指发生非期待的未知行为。这里的未知是指错误的类型不在预先定义的范围内。异常又被称为未捕获的错误(untrapped error)。程序在执行时发生未预先定义的错误,程序编译器和运行时都没有及时将其捕获处理,而是由操作系统进行异常处理。

在Go语言中对于错误提供了两种处理机制:

  • 通过函数返回错误类型的值来处理错误。
  • 通过panic打印程序调用栈,终止程序执行来处理错误。

对错误的处理也有两种方法:

  • 一种是通过返回一个错误类型值来处理错误,
  • 另一种是直接调用panic抛出错误,退出程序。

Go是静态强类型语言,程序的大部分错误是可以在编译器检测到的,但是有些错误行为需要在运行期才能检测出来。此种错误行为将导致程序异常退出。其表现出的行为就和直接调用panic一样:打印出函数调用栈信息,并且终止程序执行。

在实际的编程中,error和panic的使用应该遵循以下原则:

(1)程序发生的错误导致程序不能容错继续执行,此时程序应该主动调用panic或由运行时抛出panic。

(2)程序虽然发生错误,但是程序能够容错继续执行,此时应该使用错误返回值的方式处理错误,或者在可能发生运行时错误的非关键分支上使用recover捕获panic。

相关推荐
向宇it13 分钟前
【从零开始入门unity游戏开发之——C#篇26】C#面向对象动态多态——接口(Interface)、接口里氏替换原则、密封方法(`sealed` )
java·开发语言·unity·c#·游戏引擎·里氏替换原则
@菜鸟进阶记@16 分钟前
java根据Word模板实现动态填充导出
java·开发语言
卖芒果的潇洒农民18 分钟前
Lecture 6 Isolation & System Call Entry
java·开发语言
SomeB1oody40 分钟前
【Rust自学】6.1. 定义枚举
开发语言·后端·rust
SomeB1oody1 小时前
【Rust自学】5.3. struct的方法(Method)
开发语言·后端·rust
Kisorge2 小时前
【C语言】指针数组、数组指针、函数指针、指针函数、函数指针数组、回调函数
c语言·开发语言
啦啦右一2 小时前
Spring Boot | (一)Spring开发环境构建
spring boot·后端·spring
森屿Serien2 小时前
Spring Boot常用注解
java·spring boot·后端
轻口味3 小时前
命名空间与模块化概述
开发语言·前端·javascript
晓纪同学4 小时前
QT-简单视觉框架代码
开发语言·qt