Go 错误处理:用 panic 取代 err != nil 的模式

Go 语言的错误处理方式通常基于 err != nil 模式,这是 Go 语言的经典做法,但在某些场合,我们可以考虑使用 panic 来处理错误,尤其是在程序无法继续执行的严重错误发生时。虽然 Go 的设计哲学强调简洁与明确的错误处理,但并不排斥在适当情况下通过 panic 让程序“崩溃”并返回错误信息。本文将探讨如何在 Go 中使用 panic 取代常见的 err != nil 错误处理模式。

一、Go 中的错误处理

Go 的标准错误处理模式通常是通过返回一个 error 类型的值来指示操作是否成功。如果发生错误,通常会将错误值赋给变量 err,然后使用 if err != nil 来检查是否发生了错误。这种做法简洁且明确,但在一些极端情况下,使用 panic 可以让程序在发生不可恢复的错误时立即终止,从而避免进一步错误的积累或误操作。

简单来讲,就是在业务代码中使用 panic 的方式来替代 “永无止境” 的 if err != nil

我们一起来看看是怎么做,又有什么优缺点,互相学习一轮。

为什么想替换

在 Go 语言中 if err != nil 写的太多,还要管方法声明各种,嫌麻烦又不方便:

err := foo()
if err != nil {
     //do something..
     return err
}

err := foo()
if err != nil {
     //do something..
     return err
}

err := foo()
if err != nil {
     //do something..
     return err
}

err := foo()
if err != nil {
     //do something..
     return err
}

上述还是示例代码,比较直面。若是在工程实践,还得各种 package 跳来跳去加 if err != nil,讲更繁琐,要去关心整体的上下游。

其余更具体的就不赘述了,可以关注我的公众号翻看先前的文章。

怎么替换 err != nil

不想写 if err != nil 的代码,方式之一就是用 panic 来替代他。

示例代码如下:

func GetFish(db *sql.DB, name string) []string {
 rows, err := db.Query("select name from users where `name` = ?", name)
 if err != nil {
  panic(err)
 }
 defer rows.Close()

 var names []string
 for rows.Next() {
  var name string
  err := rows.Scan(&name)
  if err != nil {
   panic(err)
  }

  names = append(names, name)
 }

 err = rows.Err()
 if err != nil {
  panic(err)
 }

 return names
}

在上述业务代码中,我们通过 panic 的方式取代了 return err 的函数返回,自然其所关联的下游业务代码也就不需要编写 if err != nil 的代码:

func main() {
 fish1 := GetFish(db, "煎鱼")
 fish2 := GetFish(db, "咸鱼")
 fish3 := GetFish(db, "摸鱼")
 ...
}

同时在转换为使用 panic 模式的错误机制后,我们必须要在外层增加 recover 方法:

func AppRecovery() gin.HandlerFunc {
 return func(c *gin.Context) {
  defer func() {
   if err := recover(); err != nil {
    if _, ok := err.(AppErr); ok {
     // do something...
    } else {
     panic(err)
    }
   }
  }()
 }
}

每次 panic 后根据其抛出的错误进行断言,识别是否定制的 AppErr 错误类型,若是则可以进行一系列的处理动作。

否则可继续向上 panic 抛出给顶级的 Recovery 方法进行处理。

这就是一个相对完整的 panic 错误链路处理了。

优缺点

  • 从优点上来讲:
    • 整体代码结构看起来更加的简洁,仅专注于实现逻辑即可。
    • 不需要关注和编写冗杂的 if err != nil 的错误处理代码。
  • 从缺点上来讲:
    • 认知负担的增加,需要参加项目的每一个新老同学都清楚该模式,要做一个基本规范或培训。
    • 存在一定的性能开销,每次 panic 都存在用户态的上下文切换。
    • 存在一定的风险性,一旦 panic 没有 recover 住,就会导致事故。
    • Go 官方并不推荐,与 panic 本身的定义相违背,也就是 panicerror 的概念混淆。

总结

在今天这篇文章给大家分享了如何使用 panic 的方式来处理 Go 的错误,其必然有利必有有弊,需要做一个权衡了。

三、使用 panic 的优缺点

优点:
  1. 简化代码:在一些不需要错误恢复的场景中,使用 panic 可以使代码更加简洁,避免大量的 err != nil 检查,尤其是在初始化阶段。

  2. 清晰的错误处理:对于无法恢复的错误,panic 提供了立即终止程序的方式,避免错误传播到应用程序的其他部分,导致更加复杂的错误。

  3. 调试友好panic 会打印堆栈信息,帮助开发者快速定位错误发生的位置,尤其适用于开发阶段的调试。

缺点:
  1. 不可恢复:使用 panic 会立即中止程序,不能像常规错误处理那样提供机会让程序恢复。错误一旦发生,程序无法继续执行,这在某些应用场景中可能不可接受。

  2. 不符合 Go 错误处理哲学:Go 的设计哲学强调明确的错误处理和程序的正常控制流。过度使用 panic 会使程序变得难以理解和维护,违背了 Go 的简洁与可读性原则。

  3. 性能开销:虽然 panicrecover 是轻量级的,但它们仍然会引入一定的性能开销,尤其是在频繁使用的场合。

四、如何使用 panic 与 recover

Go 提供了 recover 函数,用于从 panic 中恢复程序的执行。recover 只能在 defer 函数中调用,因此它可以用于处理和恢复 panic,避免程序完全崩溃。

示例: 使用 panicrecover


go

package main import ( "fmt" ) func mayPanic() { panic("Something went wrong!") } func safeCall() { defer func() { if r := recover(); r != nil { fmt.Println("Recovered from panic:", r) } }() mayPanic() } func main() { safeCall() fmt.Println("Program continues after recovery.") }

在这个例子中,mayPanic 会触发 panic,但 safeCall 通过 deferrecover 恢复了程序的执行,使得程序不会崩溃并且能够继续执行后续的代码。

五、总结

Go 中的 panicerr != nil 各有其适用的场景。err != nil 适用于需要进一步处理的错误,而 panic 更适合用于那些无法恢复、必须立即终止程序的错误。选择使用 panic 还是 err != nil,需要根据错误的严重程度、应用的要求以及程序的设计来决定。在开发中,我们要确保在适当的场合使用 panic,同时避免过度依赖它,以免影响程序的可维护性和稳定性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

360-go-php

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值