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

相关推荐
小蒜学长2 小时前
汽车专卖店管理系统的设计与实现(代码+数据库+LW)
java·数据库·spring boot·后端·汽车
catcfm4 小时前
Java学习笔记-泛型
java·笔记·学习
FSHOW5 小时前
【独立开发日记】MQ端到端类型安全
前端·javascript·后端
柏油5 小时前
MySQL InnoDB 后台线程
数据库·后端·mysql
老华带你飞5 小时前
社区互助|基于SSM+vue的社区互助平台的设计与实现(源码+数据库+文档)
java·前端·数据库·vue.js·小程序·毕设·社区互助平台
wypywyp6 小时前
linux基础——UDP、TCP
linux·tcp/ip·udp
阿华的代码王国6 小时前
【Android】OkHttp发起GET请求 && POST请求
android·java·okhttp·网络连接
孙克旭_6 小时前
day082-初识ElasticStack
linux·运维·elasticsearch