IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

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

    煎鱼发表于 2023-12-28 12:31:55
    love 0

    大家好,我是煎鱼。

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

    func panic(v any)

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

    快速 Demo

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

    func main() {
        panic("脑子进煎鱼了")
    
        _, err := os.Create("/tmp/file")
        if err != nil {
            log.Fatalln(err)
        }
    }

    输出结果:

    $ go run demo.go 
    panic: 脑子进煎鱼了
    
    goroutine 1 [running]:
    main.main()
        /Users/eddycjy/demo.go:10 +0x25
    exit status 2

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

    一点争议

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

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

    如下代码:

    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 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 func() {
            defer func() {
                if r := recover(); r != nil {
                    fmt.Println("Recovered in f", r)
                }
            }()
            // 异步执行一些业务流程,不小心 panic 了...
            fmt.Println(user.Name)
        }()

    输出结果:

    Recovered in f runtime error: invalid memory address or nil pointer dereference
    脑子进煎鱼了

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

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

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

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

    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:

    type panicError interface {
        error
        PanicError() string
    }

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

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

    总结

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

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

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

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

    推荐阅读

    • Go Context 到底放第一个参数传,还是放结构体里?
    • Go errors 默认加堆栈信息,有用不?
    • 裁员为什么会降本增笑?


沪ICP备19023445号-2号
友情链接