适合使用 panic - recover 机制的场景
- 不可恢复的错误:
- 例如在初始化数据库连接时,如果数据库配置严重错误,如密码错误且无法通过重试等常规手段解决,使用
panic
可以快速终止程序,避免程序在错误状态下继续运行。
- 代码示例:
package main
import (
"database/sql"
_ "github.com/go - sql - driver/mysql"
)
func main() {
db, err := sql.Open("mysql", "user:wrongpassword@tcp(127.0.0.1:3306)/test")
if err!= nil {
panic(err)
}
defer db.Close()
// 后续数据库操作
}
- 原因:这种错误会导致程序无法正常提供预期功能,使用
panic - recover
机制可以快速定位问题,且 recover
可以在程序外层捕获 panic
,进行日志记录等善后操作。
- 违反程序内部不变式:
- 比如在实现一个栈数据结构时,如果代码逻辑要求栈不能为空才能执行弹出操作,但在某个地方由于逻辑错误,栈为空时却执行了弹出操作,此时可以
panic
。
- 代码示例:
package main
import (
"fmt"
)
type Stack struct {
data []int
}
func (s *Stack) Pop() int {
if len(s.data) == 0 {
panic("pop from empty stack")
}
top := s.data[len(s.data)-1]
s.data = s.data[:len(s.data)-1]
return top
}
func main() {
var s Stack
s.Pop()
}
- 原因:不变式被打破意味着程序处于一个不应该出现的状态,使用
panic
可以立即暴露问题,方便调试和修复。
不适合使用 panic - recover 机制的场景
- 业务逻辑错误:
- 例如在电商系统中,用户下单时库存不足,这是业务层面可预期的情况,不应该
panic
。
- 不适合原因:
panic - recover
机制不是用于处理业务流程中的常规错误,使用它会使代码的业务逻辑变得混乱,难以理解和维护。
- 替代方案:返回错误值。
- 代码示例:
package main
import (
"fmt"
)
func Order(productID int, quantity int) error {
// 假设这里从数据库获取库存
stock := getStock(productID)
if stock < quantity {
return fmt.Errorf("not enough stock for product %d", productID)
}
// 进行下单逻辑,如扣除库存等
return nil
}
func getStock(productID int) int {
// 模拟从数据库获取库存
return 10
}
func main() {
err := Order(1, 15)
if err!= nil {
fmt.Println(err)
}
}
- 可重试的错误:
- 比如在调用外部 API 时,偶尔因为网络波动等原因失败,这种情况不适合
panic
。
- 不适合原因:
panic
会终止当前 goroutine 的正常执行流程,对于可重试的错误,我们希望程序能够通过重试继续运行。
- 替代方案:使用重试逻辑结合错误处理。
- 代码示例:
package main
import (
"fmt"
"time"
)
func CallAPI() error {
// 模拟 API 调用失败
return fmt.Errorf("API call failed")
}
func main() {
maxRetries := 3
for i := 0; i < maxRetries; i++ {
err := CallAPI()
if err == nil {
fmt.Println("API call success")
break
}
fmt.Printf("Retry %d: API call failed, error: %v\n", i+1, err)
time.Sleep(time.Second)
}
}