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
Go 语言中的异常处理简单实践 panic、recover【GO 基础】的更多相关文章
- C语言中的异常处理
一 前言: 异常处理,对于做面向对象开发的开发者来说是再熟悉不过了,例如在C#中有 try { ... } catch( Exception e){...} finally{ ..... } 在C++ ...
- Java语言中的异常处理
Java语言中的异常处理包括声明异常.抛出异常.捕获异常和处理异常四个环节. throw用于抛出异常. throws关键字可以在方法上声明该方法要抛出的异常,然后在方法内部通过throw抛出异 ...
- 【C++】异常简述(一):C语言中的异常处理机制
人的一生会遇到很多大起大落,尤其是程序员. 程序员写好的程序,论其消亡形式无非三种:无疾而终.自杀.他杀. 当然作为一名程序员,最乐意看到自己写的程序能够无疾而终,因此尽快的学习异常处理机制是非常重要 ...
- C语言中的异常处理机制
#define try if(!setjmp(Jump_Buffer)) 返回try现场后重新执行判断,所以有两次执行. http://blog.csdn.net/tian_dao_chou_qin/ ...
- c语言中函数的简单介绍
c语言中函数的介绍: 函数,简单的说就是代码的打包.存放在一个地方,当需要的时候调用. 函数分类: 1.无参无返回值函数 void func() 2.无参有返回值函数 int func() 3.有参 ...
- C语言中可变形参简单实例
以下程序主要包括三个主要函数: 一个最简单的可变形参函数实例: 一个简单的printf功能的实例: 一个打印字符串函数(辅助): 其中myPrintf函数,实现了printf的部分简单功能,并没有去实 ...
- Go语言加解密--AES简单实践
AES加解密的简单实现,代码如下. package main import ( "crypto/aes" "crypto/cipher" "encod ...
- 021_go语言中的异常处理
代码演示 package main import ( "errors" "fmt" ) // Go语言里面约定错误代码是函数的最后一个返回值, // 并且类型是 ...
- Go语言中超过1000个线程panic
1.问题描述 2.实验 3.原理 4.解释 Close太多,Close在Windows上阻塞型的可能会新创建线程,而Linux上是非阻塞型不会新创建线程.
- go语言中使用defer、panic、recover处理异常
go语言中的异常处理,没有try...catch等,而是使用defer.panic.recover来处理异常. 1.首先,panic 是用来表示非常严重的不可恢复的错误的.在Go语言中这是一个内置函数 ...
随机推荐
- 殷浩详解DDD:如何避免写流水账代码?
简介: 在日常工作中我观察到,面对老系统重构和迁移场景,有大量代码属于流水账代码,通常能看到开发在对外的API接口里直接写业务逻辑代码,或者在一个服务里大量的堆接口,导致业务逻辑实际无法收敛,接口复用 ...
- [Mobi] TWRP 镜像 for OnePlus X
所有支持的设备列表:https://twrp.me/Devices/ 选择你的设备名:https://twrp.me/Devices/OnePlus/ https://twrp.me/oneplus/ ...
- R1_ES知识图谱
业务量增加,优化..优化... 学习... 学习..... 阮一鸣,eBay Pronto 平台技术负责人,管理了 eBay 内部上百个 Elasticsearch 集群,数据规模超过 4000 节点 ...
- 抛砖系列之文本处理工具-awk
AWK是一个优良的文本处理工具,Linux及Unix环境中现有的功能最强大的数据处理引擎之一.这种编程及数据操作语言(其名称得自于它的创始人阿尔佛雷德·艾侯.彼得·温伯格和布莱恩·柯林汉姓氏的首个字母 ...
- linux文件查找工具详解
linux文件查找详解 目录 linux文件查找详解 1.linux文件查找工具 1.1 find命令详解 1.1.1 根据文件名查找 1.1.2 根据属主属组查找 1.1.3 根据文件类型查找 1. ...
- C数据结构:KMP算法详解(呕心沥血)
KMP算法 作者心声 了解暴力求解(必需会) KMP算法详解 记住我这段话(你会爱上它的)← : ①前后缀及其用处 ②求出前后缀的next数组 求出next数组的代码 开始实现KMP算法 结尾 附上源 ...
- C语言:将有顺序的数组进行逆序排序
//设计逆向排序之,数字有序排列,进行逆向排序 主要思想就是头和尾进行交换,前提是------数字必须是排好序的才能进行逆序排 /*假设数组为: 7,8,9,10,11 1 N ...
- MLOps 学习之旅「GitHub 热点速览」
又是 AI 神仙打架的一周,上周 OpenAI 发布了最新的 GPT-4o 模型,而谷歌也紧跟着开源了 Gemma 2 模型.随着 AI 大模型不断地变强,各大科技巨头正利用它们重塑自家的产品,这也让 ...
- FolkMq v1.4.6 发布(可以内嵌的消息中间件)
功能简表 角色 功能 生产者(客户端) 发布消息.定时消息(或叫延时).顺序消息.可过期消息.事务消息.支持 Qos0.Qos1 消费者(客户端) 订阅.取消订阅.消费-ACK(自动.手动) 服务端 ...
- Java自增
Java自增 本文分为以下部分: 栗子 栗子解释 来点复杂的 字节码解读 总结 栗子 java存在一种神奇的操作符,++,自增1,但是经常分不清楚 i++ 和++i 两者的区别,虽然最后结果可能都是 ...