Go 语言中的异常处理简单实践 panic、recover【GO 基础】

〇、Go 中的异常处理简介

Golang 没有结构化异常,使用panic 抛出错误,recover 捕获错误

panic、recover 参数类型为 interface{},因此可抛出任何类型对象。

func panic(v interface{})
func recover() interface{}

处理流程:方法体重抛出一个 panic 的异常,然后在 defer 中通过 recover 捕获这个异常,然后正常处理。

关于 panic:

触发运行时错误:panic 用于立即停止当前函数的执行 ,并开始回溯调用栈直到程序终止或遇到 recover。

传递错误信息:panic 可以接受任何类型的参数 ,通常传递字符串或错误接口实例,方便错误信息的传递和处理。

易错点:随意使用 panic 处理非严重错误是不推荐的,其主要用于处理不可恢复的运行时错误,对于可处理的错误,应通过返回错误值的方式传递给调用者。

关于 recover:

捕获 panic:recover 只能在 defer 语句中调用,用于捕获当前 goroutine 发生的 panic,如果没有 panic 发生,则返回 nil。

recover 处理异常后:逻辑并不会恢复到 panic 那个点去,函数跑到 defer 之后的那个点。

易错点:recover 只能捕获同一 goroutine 内发生的 panic,对于其他 goroutine 引发的 panic 无能为力。

**Go 语言推荐使用错误返回码而非异常机制来处理错误,通过 error 接口返回错误信息,这是一种更灵活且不会破坏程序执行流程的方法。**在实际开发中,建议优先使用错误处理机制,谨慎使用 panic 和 recover,以编写出更加稳定和高效的 Go 程序。

一、异常捕获简单测试

1.1 简单的捕获 panic

如下代码,直接触发 panic,在 defer 中通过 recover 捕获,并转换成 string 输出:

package main

func main() {
	test()
}

func test() {
	defer func() {
		if err := recover(); err != nil {
			println("输出:", err.(string)) // 将 interface{} 转型为具体类型 string
			// 输出: panic error!
		}
	}()
	panic("panic error!")
}

再来个示例,往已关闭的通道中发送数据,会引发异常:

package main

import (
	"fmt"
)

func main() {
	defer func() {
		if err := recover(); err != nil {
			fmt.Println(err) // 输出:send on closed channel
		}
	}()

	var ch chan int = make(chan int, 10)
	close(ch) // 关闭通道
	ch <- 1   // 继续往通道中发送值,就会引发异常
}

1.2 多个异常时 recover 如何捕获?

延迟调用中引发的错误,可被后续延迟调用捕获,但仅最后一个错误可被捕获。

如下代码,只有 defer 中的 panic 会被捕获,另一个异常将会漏掉:

package main

import "fmt"

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

func main() {
	test()
}
// 输出:
// defer panic

因此,需要再第二个 defer 中针对 test panic 进行处理。

1.3 一个异常在多层 defer 如何捕获?

捕获函数 recover 只有在延迟调用内直接调用 才会终止错误,否则总是返回 nil。任何未捕获的错误都会沿调用堆栈向外传递。

如下代码,在第一层进行了延迟调用,然后第〇层就未获取到 panic:

package main

import "fmt"

func test() {
	defer func() {
		fmt.Println("第〇层:", recover()) // 无效
	}()
	defer func() {
		fmt.Println("第一层:", recover()) // 有效
	}()
	defer fmt.Println("第二层:", recover()) // 无效!
	defer fmt.Println("第三层:", recover()) // 无效!
	defer func() {
		func() {
			println("第四层:defer inner")
			fmt.Println("第四层:", recover()) // 无效!
		}()
	}()
	panic("test panic")
}

func main() {
	test()
}
// 输出:
// 第四层:defer inner
// 第四层: <nil>
// 第三层: <nil>
// 第二层: <nil>
// 第一层: test panic
// 第〇层: <nil>

1.4 通过 error 类型的错误对象来表示函数调用状态

除用 panic 引发中断性错误外,还可返回 error 类型错误对象来表示函数调用状态。

type error interface {
    Error() string
}

标准库 errors.New 和 fmt.Errorf 函数用于创建实现 error 接口的错误对象。通过判断错误对象实例来确定具体错误类型。

package main

import (
	"errors"
	"fmt"
)

var ErrDivByZero = errors.New("division by zero") // 定义错误类型 ErrDivByZero

func div(x, y int) (int, error) {
	if y == 0 {
		return 0, ErrDivByZero
	}
	return x / y, nil
}

func main() {
	defer func() {
		fmt.Println(recover()) // 捕获 panic,无 panic 就打印 <nil>
	}()
	switch z, err := div(10, 0); err { // div(10, 0) 返回 ErrDivByZero
	case nil:
		println(z)
	case ErrDivByZero: // 触发 panic
		panic(err)
	}
}

// 输出:
// division by zero

1.5 通过将代码块重构成匿名函数来实现 try-catch 的效果

将代码块重构成匿名函数,并包含异常处理,如此可确保后续代码被执行

如下代码,当被除数为 0 时会报错,在匿名函数中被捕获并记录,然后不影响正常输出:

package main

import "fmt"

func test(x, y int) {
	var z int
	func() {
		defer func() {
			if err := recover(); err != nil {
				fmt.Println("err:", err)
				z = 0
			}
		}()
		z = x / y
	}()
	fmt.Printf("x / y = %d\n", z)
}

func main() {
	test(2, 0)
}
// 输出:
// err: runtime error: integer divide by zero
// x / y = 0

1.6 实现类似 try-catch 的异常处理

package main

import "fmt"

func Try(fun func(), handler func(interface{})) {
	defer func() {
		if err := recover(); err != nil {
			handler(err) // 捕获异常后,执行异常处理逻辑
		}
	}()
	fun() // 直接执行处理逻辑
}

func main() {
	Try(
		func() {
			// 程序处理逻辑。。。
			panic("test panic") // 抛出异常
		},
		func(err interface{}) {
			// 异常处理逻辑。。。
			fmt.Println(err)
		},
	)
}

参考:http://www.topgoer.com/%E5%87%BD%E6%95%B0/%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86.html

相关推荐
煎鱼eddycjy7 小时前
新提案:由迭代器启发的 Go 错误函数处理
go
煎鱼eddycjy7 小时前
Go 语言十五周年!权力交接、回顾与展望
go
不爱说话郭德纲1 天前
聚焦 Go 语言框架,探索创新实践过程
go·编程语言
0x派大星2 天前
【Golang】——Gin 框架中的 API 请求处理与 JSON 数据绑定
开发语言·后端·golang·go·json·gin
IT书架2 天前
golang高频面试真题
面试·go
郝同学的测开笔记2 天前
云原生探索系列(十四):Go 语言panic、defer以及recover函数
后端·云原生·go
秋落风声3 天前
【滑动窗口入门篇】
java·算法·leetcode·go·哈希表
0x派大星5 天前
【Golang】——Gin 框架中的模板渲染详解
开发语言·后端·golang·go·gin
0x派大星5 天前
【Golang】——Gin 框架中的表单处理与数据绑定
开发语言·后端·golang·go·gin
三里清风_6 天前
如何使用Casbin设计后台权限管理系统
golang·go·casbin