新提案:Go panic 能不能加 PanicError?

大家好,我是煎鱼。

在我们学习和平时使用 Go 时,一定会涉及到一个内置函数 panic:

go 复制代码
func panic(v any)

调用该函数后会停止代码的控制流程并开始恐慌,达到扭转当前程序控制流的目的。在使用上也常常和 defer 和 recover 关联上。

快速 Demo

以下是一个简单的使用 Demo:

go 复制代码
func main() {
	panic("脑子进煎鱼了")

	_, err := os.Create("/tmp/file")
	if err != nil {
		log.Fatalln(err)
	}
}

输出结果:

go 复制代码
$ go run demo.go 
panic: 脑子进煎鱼了

goroutine 1 [running]:
main.main()
	/Users/eddycjy/demo.go:10 +0x25
exit status 2

看着都没什么问题。输出结果符合预期。

一点争议

由于 Go 起协程(goroutine)非常简单、方便,因此绝大部分开发者在应用程序中会经常用 goroutine 去做各种并发处理的逻辑,一看不小心。就很有可能会引发程序中的 panic,导致整个应该程序崩溃,出现事故。(见过好几起低级错误了)

有一个比较常见触发的场景之一:空指针调用。时不时就能见到几个应用又诱发了。

如下代码:

go 复制代码
type T struct {
	Name string
}

func main() {
	var user *T
	go func() {
		// 异步执行一些业务流程,不小心 panic 了...
		fmt.Println(user.Name)
	}()

	// 做一些事情...
	time.Sleep(time.Second * 1)

	fmt.Println("脑子进煎鱼了")
}

输出结果:

go 复制代码
$ go run demo.go
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x8 pc=0x1087178]

goroutine 6 [running]:
main.main.func1()
	/Users/eddycjy/demo.go:16 +0x18
created by main.main in goroutine 1
	/Users/eddycjy/demo.go:14 +0x31
exit status 2

当然,这也是有办法解决的。标准的方式是通过 recover,捕获 panic。如下代码:

go 复制代码
go func() {
		defer func() {
			if r := recover(); r != nil {
				fmt.Println("Recovered in f", r)
			}
		}()
		// 异步执行一些业务流程,不小心 panic 了...
		fmt.Println(user.Name)
	}()

输出结果:

go 复制代码
Recovered in f runtime error: invalid memory address or nil pointer dereference
脑子进煎鱼了

又或是基于 goroutine+recvoer 封装一个协程调用的方法。要求使用这类工具库来规避这个 "坑"。

但不得不说,很多同学崩就崩在不觉得这个地方会出问题,但就是有问题。最后只能一溜烟全都用封装好的工具库来起 goroutine 了。

新提案:可定义 panic 错误信息

在前面的案例中,我们可以看到 panic 后现在的输出信息如下:

go 复制代码
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x8 pc=0x1087178]

goroutine 6 [running]:
main.main.func1()
	/Users/eddycjy/demo.go:16 +0x18
created by main.main in goroutine 1
	/Users/eddycjy/demo.go:14 +0x31
exit status 2

程序输出了恐慌值和 goroutine 堆栈跟踪。

这第一眼看起来是非常迷惑的,要看错误信息。如果是程序内抛的空指针,还要去翻堆栈信息去猜,再看是哪里的程序。做一轮排查、定位、验证。

因此社区里 @Mitar 提出了《proposal: runtime: provide a way to format output in unhandled panics》的提案。希望可以针对意外情况+无人处理的 panic 错误进行自定义的格式化处理。

提案中希望 panic 结构体新增 PanicError:

go 复制代码
type panicError interface {
    error
    PanicError() string
}

如果值实现了该接口,则会优先调用 PanicError 方法,为错误处理提供一个可选选项,可以为调试补充额外的有用信息。

这样就可以进一步区分出 Panic 错误和普通 Error 错误的方法,并且针对 Panic 的错误做各种奇怪的操作和补充。

总结

今天给大家分享了社区对于 panic 优化的一个小点。原提案作者的目的是为了针对 panic 错误新增 PanicError 方法,若存在则优先使用该方法,而非与普通 error 共用 Error 方法,并以此去做好区分识别和实现。

在 Go 中对 panic 的优化,官方一直都是比较迟缓的。一方面是大佬们比较少写业务代码,另外一方面是类似对 panic 加全局拦截器避免崩溃等方式,也比较违背开创语言时的哲学宗旨。

大家对于 panic 的语言使用层面如果有更好的想法和建议,也欢迎随时留言交流!

文章持续更新,可以微信搜【脑子进煎鱼了】阅读,本文 GitHub github.com/eddycjy/blo... 已收录,学习 Go 语言可以看 Go 学习地图和路线,欢迎 Star 催更。

推荐阅读

相关推荐
梦想很大很大3 小时前
使用 Go + Gin + Fx 构建工程化后端服务模板(gin-app 实践)
前端·后端·go
lekami_兰8 小时前
MySQL 长事务:藏在业务里的性能 “隐形杀手”
数据库·mysql·go·长事务
却尘11 小时前
一篇小白也能看懂的 Go 字符串拼接 & Builder & cap 全家桶
后端·go
ん贤12 小时前
一次批量删除引发的死锁,最终我选择不加锁
数据库·安全·go·死锁
mtngt111 天前
AI DDD重构实践
go
Grassto3 天前
12 go.sum 是如何保证依赖安全的?校验机制源码解析
安全·golang·go·哈希算法·go module
Grassto4 天前
11 Go Module 缓存机制详解
开发语言·缓存·golang·go·go module
程序设计实验室5 天前
2025年的最后一天,分享我使用go语言开发的电子书转换工具网站
go
我的golang之路果然有问题5 天前
使用 Hugo + GitHub Pages + PaperMod 主题 + Obsidian 搭建开发博客
golang·go·github·博客·个人开发·个人博客·hugo
啊汉7 天前
古文观芷App搜索方案深度解析:打造极致性能的古文搜索引擎
go·软件随想