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 是轻量级的,但它们仍然会引入一定的性能开销,尤其是在频繁使用的场合。

四、如何使用 panicrecover

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,同时避免过度依赖它,以免影响程序的可维护性和稳定性。

相关推荐
MarcoPage6 分钟前
Python 字典推导式入门:一行构建键值对映射
java·linux·python
脸大是真的好~7 分钟前
黑马JAVAWeb-11 请求参数为数组-XML自动封装-XML手动封装-增删改查-全局异常处理-单独异常分别处理
java
未来之窗软件服务1 小时前
服务器运维(六)跨域配置 Preflight 问题——东方仙化神期
运维·服务器·服务器运维·仙盟创梦ide·东方仙盟
埃伊蟹黄面1 小时前
计算机的“身体”与“灵魂”:冯·诺依曼架构与操作系统
linux
AORO20252 小时前
智能三防手机哪款好?22000mAh+夜视+露营灯打造专业户外装备
服务器·网络·智能手机·电脑·1024程序员节
Hello.Reader3 小时前
Data Sink定义、参数与可落地示例
java·前端·网络
winner88813 小时前
Linux 软件安装 “命令密码本”:yum/apt/brew 一网打尽
linux·运维·服务器
九河云4 小时前
软件开发平台 DevCloud
运维·服务器·数据库·科技·华为云
2401_837088504 小时前
stringRedisTemplate.opsForHash().entries
java·redis
firstacui4 小时前
DNS高速缓存&分离解析
服务器