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
写的太多,还要管方法声明各种,嫌麻烦又不方便:
go
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
来替代他。
示例代码如下:
go
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
的代码:
css
func main() {
fish1 := GetFish(db, "煎鱼")
fish2 := GetFish(db, "咸鱼")
fish3 := GetFish(db, "摸鱼")
...
}
同时在转换为使用 panic
模式的错误机制后,我们必须要在外层增加 recover
方法:
go
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
本身的定义相违背,也就是panic
与error
的概念混淆。
-
三、使用 panic
的优缺点
优点:
- 简化代码 :在一些不需要错误恢复的场景中,使用
panic
可以使代码更加简洁,避免大量的err != nil
检查,尤其是在初始化阶段。 - 清晰的错误处理 :对于无法恢复的错误,
panic
提供了立即终止程序的方式,避免错误传播到应用程序的其他部分,导致更加复杂的错误。 - 调试友好 :
panic
会打印堆栈信息,帮助开发者快速定位错误发生的位置,尤其适用于开发阶段的调试。
缺点:
- 不可恢复 :使用
panic
会立即中止程序,不能像常规错误处理那样提供机会让程序恢复。错误一旦发生,程序无法继续执行,这在某些应用场景中可能不可接受。 - 不符合 Go 错误处理哲学 :Go 的设计哲学强调明确的错误处理和程序的正常控制流。过度使用
panic
会使程序变得难以理解和维护,违背了 Go 的简洁与可读性原则。 - 性能开销 :虽然
panic
和recover
是轻量级的,但它们仍然会引入一定的性能开销,尤其是在频繁使用的场合。
四、如何使用 panic
与 recover
Go 提供了 recover
函数,用于从 panic
中恢复程序的执行。recover
只能在 defer
函数中调用,因此它可以用于处理和恢复 panic
,避免程序完全崩溃。
示例: 使用 panic
和 recover
go
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
通过 defer
和 recover
恢复了程序的执行,使得程序不会崩溃并且能够继续执行后续的代码。
五、总结
Go 中的 panic
和 err != nil
各有其适用的场景。err != nil
适用于需要进一步处理的错误,而 panic
更适合用于那些无法恢复、必须立即终止程序的错误。选择使用 panic
还是 err != nil
,需要根据错误的严重程度、应用的要求以及程序的设计来决定。在开发中,我们要确保在适当的场合使用 panic
,同时避免过度依赖它,以免影响程序的可维护性和稳定性。