11.异常处理

11.异常处理

    在大部分的编程语言都支持异常处理机制,例如比较常见的try...catch...finally。但Go语言的设计者认为其他语言的异常处理太过消耗资源,且设计和处理比较复杂。导致使用者不能很好的处理错误,甚至觉得处理麻烦,从而忽视或忽略错误,导致程序崩溃。为了解决这些问题,Go将错误处理设计的非常简单,如下所示:

    函数调用时,允许返回多会个值,且最后一个返回值可以是error接口类型的值

  • 如果调用产生错误,则这个值是一个error接口类型的错误
  • 如果调用成功,则该值为nil

    通过检查函数的返回值,如果为nil,则代表函数处理成功,否则则进行异常处理

11.1 error

    error是Go中声明的接口类型,其定义如下所示:

type error interface {
	Error() string
}

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

    除以上方法,还可以实现自定义error,可使用error包中的New方法返回一个error接口类型的错误实例(errors.New("This is error")) ,示例代码如下所示:

package main

import (
	"errors"
	"fmt"
)

type ErrorSamle struct {
	e string
}

func (e *ErrorSamle) Error() string {
	return e.e
}

// 因为ErrorSample已经实现了接口error的方法,因此这里可以使用构造函数NewErrorSample返回Error接口的一个实例
func NewErrorSample(e string) error {
	return &ErrorSamle{e: e}
}

var ErrorDivsionByZero = errors.New("divsion is zero")

func Div(a, b float32) (float32, error) {
	if b == 0 {
		return 0.0, ErrorDivsionByZero
	}
	return a / b, nil
}

func main() {
	if v, err := Div(1, 0); err == nil {
		fmt.Printf("计算成功,值为%v\n", v)
	} else {
		fmt.Printf("计算错误,错误为:%v\n", err)
	}

	err:=NewErrorSample("自定义错误")
	fmt.Printf("自定义错误 - %v\n",err)
}

    运行结果如下所示:

计算错误,错误为:divsion is zero
自定义错误 - 自定义错误

11.2 panic

    panic有时也称之为宕机。因为当其发生时,往往会造成程序崩溃、服务终止等后果。但如果在错误发生时,不及时panic而终止程序运行时,可能会生成更大的损失,因此及时终止程序,可以做到及时止损。因为panic的主要用例是让开发人员主动抛出异常,使程序进入宕机状态而终止运行。其产生的主要原因如下所示:

  • runtime运行时错误导致抛出panic。例如数组越界、内存溢出等
  • 主动抛出panic

    panic的基本语法如下所示:

func panic(v any)

panci就一个参数 v,类型为空接口,传入为异常信息

    示例代码如下所示:

package main

import "fmt"

func Div1(a, b float32) float32 {
	if b == 0 {
		panic("除数为0")
	}
	return a / b
}

func Div2(a, b float32) float32 {
	// 这里除法,没有判断除数为0的场景,可能会出现panic
	return a / b
}

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

    代码运行结果如下所示:

panic: 除数为0

goroutine 1 [running]:
main.Div1(0x40a00000, 0x0)
	c:/Users/Surpass/Documents/GolangProjets/src/go-learning-note/02-代码/11/1102-panic/main.go:7 +0x4f
main.main()
	c:/Users/Surpass/Documents/GolangProjets/src/go-learning-note/02-代码/11/1102-panic/main.go:18 +0x2a
Process 29488 has exited with status 2

11.3 defer

    defer 本意推迟或延迟的意思,在Go语言则是起到延迟执行的作用。其语法也在学简单,在正常的语句前添加defer就可了。在函数中使用defer语句,会使得defer后跟的语句进行延迟处理,当该函数即将返回panic,defer语句开始执行。

    同一个函数可以拥有多个defer语句,依次加入调用栈(LIFO),函数返回或panic时,从栈顶依次执行defer后面的语句。执行的先后顺序和注册的顺序刚好相反(先注册的后执行)。

  • 示例代码-1:
package main

import "fmt"

func PrintNumer(number int) {
	fmt.Printf("当前数值为:%d\n", number)
}

func main() {
	fmt.Println("函数开始运行")
	defer PrintNumer(1)
	defer PrintNumer(2)
	defer PrintNumer(3)
	defer PrintNumer(4)
	fmt.Println("函数结束运行")
}

    代码运行结果如下所示:

函数开始运行
函数结束运行
当前数值为:4
当前数值为:3
当前数值为:2
当前数值为:1
  • 示例代码-2
package main

import "fmt"

func PrintNumer(number int) {
	fmt.Printf("当前数值为:%d\n", number)
}

func main() {
	number := 1
	fmt.Println("函数开始运行")
	defer PrintNumer(number)
	number++
	defer PrintNumer(number)
	defer PrintNumer(number)
	number++
	defer PrintNumer(number)
	fmt.Println("函数结束运行")
}

    代码运行结果如下所示:

函数开始运行
函数结束运行
当前数值为:3
当前数值为:2
当前数值为:2
当前数值为:1
  • 示例代码-3
package main

import "fmt"

func PrintNumer(number int) {
	fmt.Printf("当前数值为:%d\n", number)
}

func main() {
	number := 1
	fmt.Println("函数开始运行")
	defer func(number int) { fmt.Printf("defer 延迟打印%d", number) }(number)
	number++
	defer PrintNumer(number)
	defer PrintNumer(number)
	number++
	defer PrintNumer(number)
	fmt.Println("函数结束运行")
}

    代码运行结果如下所示:

函数开始运行
函数结束运行
当前数值为:3
当前数值为:2
当前数值为:2
defer 延迟打印1
  • 示例代码-4
package main

import "fmt"

func PrintNumer(number int) {
	fmt.Printf("当前数值为:%d\n", number)
}

func main() {
	number := 1
	fmt.Println("函数开始运行")
	defer func() { fmt.Printf("defer 延迟打印%d", number) }()
	number++
	defer PrintNumer(number)
	defer PrintNumer(number)
	number++
	defer PrintNumer(number)
	fmt.Println("函数结束运行")
}

    代码运行结果如下所示:

函数开始运行
函数结束运行
当前数值为:3
当前数值为:2
当前数值为:2
defer 延迟打印3
  • 示例代码-5
package main

import "fmt"

var number int = 10

func PrintNumer(number int) {
	fmt.Printf("当前数值为:%d\n", number)
}

func PrintNumer2() {
	fmt.Printf("number 的值为:%d\n", number)
}

func main() {
	number := 1
	fmt.Println("函数开始运行")
	defer func() { fmt.Printf("defer number is:%d\n", number) }() // 3
	defer PrintNumer(number)                                      // 1
	defer PrintNumer2()                                           // 注意变量作用域 10
	number++
	defer PrintNumer(number) // 2
	number++
	defer PrintNumer(number) // 3
	fmt.Println("函数结束运行")
}

    代码运行结果如下所示:

函数开始运行
函数结束运行
当前数值为:3
当前数值为:2
number 的值为:10
当前数值为:1
defer number is:3

    通过以上示例代码,总结如下所示:

  • 如果函数时中存在defer,则在函数即将返回倒序执行defer后面的语句
  • 如果defer中语句含有变量时,则其值在注册时计算
  • 如果defer中语句为匿名函数且无参数时,没有办法准备参数。延迟执行时,需要重新计算函数语句块的变量
  • 如果defer中语句为匿名函数且有参数时,可以准备参数,即当时注册时的变量值
  • 一个函数可以拥有多个defer

defer在日常开发中,应用比较多,例如打开文件、连接数据库、sync同步等待等

11.4 recover

    在使用panic抛出异常后,程序会停止执行,如果想让程序在异常情况下他能继续执行,可以将defer和recover结合起来,实现异常捕捉和恢复。类似于其他编程语言中的try-catch机制。

    recover是内置函数,返回值为空接口,代表程序的异常信息,必须与defer一起使用才能实现异常的捕捉和处理。示例代码如下所示:

package main

import (
	"errors"
	"fmt"
)

var ErrorDivsionByZero = errors.New("除数为0")

func Div(a, b float32) float32 {
	// 定义执行延迟执行的匿名函数
	defer func() {
		if err := recover(); err != nil {
			// 异常不为空,主动抛出异常
			fmt.Printf("捕捉到异常:%v", err)
		} else {
			fmt.Println("程序无异常")
		}
	}()

	if b == 0 {
		panic(ErrorDivsionByZero)
	}
	return a / b
}

func main() {
	// 正常情况
	Div(10, 5)
	// 异常情况
	Div(10, 0)
}

    运行结果如下所示:

程序无异常
捕捉到异常:除数为0

    在上面代码中,一旦在函数中发现panic,当前函数panic之后的语句不再执行,开始执行defer。如果在defer中错误被recover后,就相当于当前函数产生的错误得到了处理。当前函数执行完defer后退出当前函数,程序还可以从当前函数之后继续执行。在结合defer和recover捕捉和处理异常时,具备以下特点:

  • 有panic,但没有recover,就没有地方处理错误,程序会出现崩溃
  • 有panic,也有recover来捕捉时,则相当于错误被处理。当前defer执行完之后,退出当前函数,继续执行后续函数。

本文同步在微信订阅号上发布,如各位小伙伴们喜欢我的文章,也可以关注我的微信订阅号:woaitest,或扫描下面的二维码添加关注:

posted @ 2025-08-26 00:21  Surpassme  阅读(6)  评论(0)    收藏  举报