15、异常处理

异常处理

​ Go的设计者认为其它语言异常处理太过消耗资源,且设计和处理复杂,导致使用者不能很好的处理错误,甚至觉得异常和错误处理起来麻烦而忽视、忽略掉,从而导致程序崩溃。

​ 为了解决这些问题,Go将错误处理设计的非常简单

  • 函数调用,返回值可以返回多值,一般最后一个值可以是error接口类型的值
    • 如果函数调用产生错误,则这个值是一个error接口类型的错误
    • 如果函数调用成功,则该值是nil
  • 检查函数返回值中的错误是否是nil,如果不是nil,进行必要的错误处理

error是Go中声明的接口类型

type error interface {
   
  Error() string
}
//已经写入到go的源码中了

所有实现 Error() string 签名的方法,都可以实现错误接口。用Error()方法返回错误的具体描述。

自定义error

package main

import "fmt"

// 定义一个实现了error接口的自定义错误类型 errorString
type errorString struct {
   
	s string
}

// 实现 errorString 的 Error 方法,使其满足 error 接口
func (e *errorString) Error() string {
   
	return e.s
}
// New 函数用于创建并返回一个新的 errorString 实例,接受一个字符串参数作为错误信息
func New(text string) error {
   
	return &errorString{
   text}
}

func main() {
   
    // 创建一个 errorString 类型的变量 e,并初始化其值为 "错误理由1"
	var e = errorString{
   "错误理由1"}
    // 打印 e 的值、通过 Error 方法获取错误信息、e 的地址以及通过指针调用 Error 方法获取错误信息
	fmt.Println(e, e.Error(), &e, (&e).Error())

    // 使用 New 函数创建一个错误,错误信息为 "错误理由2"
	var err = New("错误理由2")
    // 打印通过 Error 方法获取的错误信息
	fmt.Println(err.Error())  //e和err的区别
}

在这个程序中,通过定义一个实现了 error 接口的自定义类型 errorString,并在其中实现了 Error 方法,使其可以用作错误类型。然后通过 New 函数创建了两个错误实例,打印访问这些错误实例的错误信息。

e 变量:
e 是直接创建的 errorString 类型的变量,其值为 errorString{
   "错误理由1"}。
errorString 是一个自定义的类型,实现了 error 接口。它包含一个字符串字段 s 和一个实现 Error 方法的函数。
因此,e 的类型是 errorString,但由于实现了 error 接口,可以被赋值给 error 类型的变量。

err 变量:
err 是通过 New 函数创建的,该函数返回一个 error 接口类型的指针,指向一个 errorString 类型的实例。其值为 &errorString{
   "错误理由2"}。
因为 New 函数返回的是 error 接口类型,所以 err 的类型是 error 接口。
虽然底层实现是 errorString 类型,但通过 error 接口,只能访问 error 接口定义的方法,即 Error 方法。
总体来说,e 和 err 都代表实现了 error 接口的错误实例,但 e 是直接的 errorString 类型的实例,而 err 是通过 New 函数返回的 error 接口类型的指针,指向 errorString 类型的实例。
package main

import (
	"errors"
	"fmt"
)

var ErrDivisionByZero = errors.New("division by zero") // 构造一个错误实例,因为在go的源码中已经写好了函数func New(text string) error {return &errorString{text}}

func div(a, b int) (int, error) {
   
	if b == 0 {
   
		return 0, ErrDivisionByZero
	}
	return a / b, nil
}
func main() {
   
	if r, err := div(5, 0); err != nil {
   
		fmt.Println(err.Error())
	} else {
   
		fmt.Println(r)
	}
}

division by zero

ErrDivisionByZero 是一个全局变量,它是通过errors.New函数创建的一个错误实例,表示除零错误。div 函数用于执行除法操作,如果除数为零,则返回 ErrDivisionByZero 错误;否则返回计算结果和 nil 表示没有错误。在 main 函数中,调用 div 函数并根据返回的错误信息进行相应的处理,如果发生除零错误,则打印错误信息,否则打印计算结果。

panic

​ panic是不好的,因为它发生时,往往会造成程序崩溃、服务终止等后果,所以没人希望它发生。但是,如果在错误发生时,不及时panic而终止程序运行,继续运行程序恐怕造成更大的损失,付出更加惨痛的代价。所以,有时候,panic导致的程序崩溃实际上可以及时止损,只能两害相权取其轻。

​ panic虽然不好,体验很差,但也是万不得已,可以马上暴露问题,及时发现和纠正问题。

panic产生

  • runtime运行时错误导致抛出panic,比如数组越界、除零
  • 主动手动调用panic(reason),这个reason可以是任意类型

panic执行

  • 逆序执行当前已经注册过的goroutine的defer链(recover从这里介入)
  • 打印错误信息和调用堆栈
  • 调用exit(2)结束整个进程
package main

import "fmt"

func div(a, b int) int {
   
	defer fmt.Println(1, "start")
	defer fmt.Println(2, a, b)
	r := a / b
	fmt.Println(3, "end")
	return r
}

func main() {
   
	fmt.Println(div(5, 0))
}


运行后程序崩溃,因为除零异常,输入如下
2 5 0
1 start
panic: runtime error: integer divide by zero

goroutine 1 [running]:
main.div(0x5, 0x0)
	f:/goprpject/test1/main.go:8 +0x230
main.main()
	f:/goprpject/test1/main.go:14 +0x25
exit status 2

这是因为在r := a/b这一行已经panic了,所以后面不执行了,则defer从下往上,输出

recover

​ recover即恢复,defer和recover结合起来,在defer中调用recover来实现对错误的捕获和恢复,让代码在发生panic后通过处理能够继续运行。类似其它语言中try/catch。

package main

import (
	"errors"
	"fmt"
	"runtime"
)

// 创建一个全局变量 ErrDivisionByZero,表示除零异常
var ErrDivisionByZero = errors.New("除零异常")

// 定义一个除法函数,接受两个整数参数,执行除法操作,同时可能会引发panic异常
func div(a, b int) int {
   
	// 使用defer延迟执行函数,用于处理panic异常
	defer func() {
   
		// 使用recover捕获panic异常,并在defer中进行处理
		err := recover()
		fmt.Printf("1 %+v, %[1]T\n", err)
	}()
	
	// 在defer中输出"start"
	defer fmt.Println("start")
	
	// 在defer中输出a和b的值
	defer fmt.Println(a, b)
	
	// 在defer中进行处理panic异常的代码块
	defer func() {
   
		// 再次使用recover捕获panic异常
		err := recover()
		fmt.Printf("2 %+v, %[1]T\n", err)
		
		// 使用类型断言判断panic异常的类型,并根据类型进行不同的处理
		switch v := err.(type) {
   
		case runtime.Error:
			// 判断是否为runtime.Error类型,如果是,输出具体的原因
			fmt.Printf("原因:%T, %#[1]v\n", v)
		case []int:
			// 判断是否为切片类型,如果是,输出"原因:切片"
			fmt.Println("原因:切片")
		}
		
		fmt.Println("离开recover处理")
	}()
	
	// 执行正常的除法操作
	r := a / b
	
	// 引发panic异常,同时传递一个切片作为panic的值
	panic([]int{
   1, 3, 5})
	
	// 这里的代码不会被执行,因为上面的panic会导致函数中断
	fmt.Println("end")
	
	// 返回计算结果
	return r
}

func main() {
   
	// 调用除法函数,可能引发panic异常
	fmt.Println(div(5, 0), "!!!")
	
	// 在主函数中输出"main exit"
	fmt.Println("main exit")
}


2 runtime error: integer divide by zero, runtime.errorString
原因:runtime.errorString, "integer divide by zero"
离开recover处理
5 0
start
1 <nil>, <nil>
0 !!!
main exit

​ 上例中,一旦在某函数中panic,当前函数panic之后的语句将不再执行,开始执行defer。如果在defer中错误被recover后,就相当于当前函数产生的错误得到了处理。当前函数执行完defer,当前函数退出执行,程序还可以从当前函数之后继续执行。

​ 可以观察到panic和recover有如下:

  • 有panic,一路向外抛出,但没有一处进行recover,也就是说没有地方处理错误,程序崩溃
  • 有painc,有recover来捕获,相当于错误被处理掉了,当前函数defer执行完后,退出当前函数,从当前函数之后继续执行

相关推荐

  1. 15异常处理

    2024-01-03 17:30:04       56 阅读
  2. 10-异常处理

    2024-01-03 17:30:04       49 阅读
  3. C++ 入门13异常处理

    2024-01-03 17:30:04       19 阅读
  4. Oracle 19c RAC 心跳异常处理

    2024-01-03 17:30:04       22 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-01-03 17:30:04       91 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-01-03 17:30:04       97 阅读
  3. 在Django里面运行非项目文件

    2024-01-03 17:30:04       78 阅读
  4. Python语言-面向对象

    2024-01-03 17:30:04       88 阅读

热门阅读

  1. C++系列十一:C++指针

    2024-01-03 17:30:04       57 阅读
  2. 后端开发——jdbc的学习(一)

    2024-01-03 17:30:04       58 阅读
  3. 今日学习的是mysql-事物

    2024-01-03 17:30:04       65 阅读
  4. 算法练习Day27 (Leetcode/Python-贪心算法)

    2024-01-03 17:30:04       52 阅读
  5. Linux内核--进程管理(十二)共享内存和信号量

    2024-01-03 17:30:04       47 阅读
  6. MongoDB CRUD 概述

    2024-01-03 17:30:04       56 阅读
  7. axios post YII2无法接收post参数问题解决

    2024-01-03 17:30:04       38 阅读
  8. pytorch 两个tensor的交集

    2024-01-03 17:30:04       51 阅读
  9. 通过 nvm 管理 Node 版本

    2024-01-03 17:30:04       64 阅读
  10. Unity 打包前,通过代码对 AndroidManifest 增删改查

    2024-01-03 17:30:04       63 阅读