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。

相关推荐
重生之后端学习2 分钟前
day08-Elasticsearch
后端·elasticsearch·spring cloud·中间件·全文检索·jenkins
lightqjx6 分钟前
【数据结构】顺序表(sequential list)
c语言·开发语言·数据结构·算法
巨人张15 分钟前
信息素养Python编程题
开发语言·python
志辉AI编程37 分钟前
别人还在入门,你已经精通!Claude Code进阶必备14招
后端·ai编程
阿猿收手吧!39 分钟前
【计算机网络】HTTP1.0 HTTP1.1 HTTP2.0 QUIC HTTP3 究极总结
开发语言·计算机网络
JAVA学习通40 分钟前
图书管理系统(完结版)
java·开发语言
代码老y1 小时前
Spring Boot项目中大文件上传的高级实践与性能优化
spring boot·后端·性能优化
paishishaba1 小时前
处理Web请求路径参数
java·开发语言·后端
七七七七071 小时前
C++类对象多态底层原理及扩展问题
开发语言·c++