go中error与panic本质不同:error是需显式检查的值,用于业务错误;panic是程序级崩溃信号,仅适用于不可恢复的致命错误,如配置加载失败或空指针解引用。

Go里error和panic根本不是一回事
别把业务错误扔进panic,也别指望recover能兜住所有问题。Go的error是值,要显式检查;panic是程序级崩溃信号,只该用于真正不可恢复的场景(比如配置加载失败、DB连接池初始化失败)。HTTP handler里出现user not found,这是error,不是panic;但nil map赋值或nil pointer dereference,才是panic该管的事。
常见错误现象:
- 在handler里写
panic("user not found"),结果框架返回500且堆栈泄露 - 用
strings.Contains(err.Error(), "not found")做分支判断,一加错误包装就失效 - 中间件里没包
defer/recover,自己panic了却没人捕获
Gin等框架中自定义错误响应必须靠类型断言
框架自带的gin.Recovery()只返回500,不区分错误语义。要实现“401返回Unauthorized、404返回Not Found”,就得自己写中间件,在recover()后对返回值做类型判断。
实操要点:
- 定义结构化错误类型,比如
type AppError struct { Code int; Message string },并实现Error() string - 在
CustomRecovery中间件里用switch e := err.(type)匹配*AppError、error、其他类型三档优先级 - 不要用
err == ErrUserNotFound判断,要用errors.Is(err, ErrUserNotFound)——后者能穿透%w包装 - 第三方库错误(如
pgx.ErrNoRows)必须用errors.As(err, &pgErr)提取,不能依赖字符串
跨goroutine panic无法被主recover捕获
你在HTTP handler里起一个go func() {}(),里面panic了,外层CustomRecovery中间件完全收不到。这是Go调度模型决定的:recover只对同goroutine生效。
解决方式很直接:
- 每个独立goroutine内部必须自己包
defer func() { if e := recover(); e != nil { /* 记录日志或上报 */ } }() - 不要在goroutine里调用
log.Fatal或os.Exit,那会杀掉整个进程 - 异步任务(如发消息、写日志)建议封装成带recover的通用函数,避免重复写
错误包装时%w右侧必须判空,否则直接panic
fmt.Errorf("read config: %w", err)看着没问题,但如果err是nil,这行代码运行时就会panic。这不是理论风险,而是真实高频踩坑点。
正确写法只有两种:
- 先判断:
if err != nil { return fmt.Errorf("read config: %w", err) } - 用
errors.Join合并多个非空error(Go 1.20+)
另外,Unwrap()方法必须实现才能让errors.Is和errors.As穿透到原始错误。如果只返回nil或漏写,上层就永远拿不到底层哨兵错误。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











