recover只能在defer中捕获当前goroutine的panic,需写成defer func(){if r:=recover();r!=nil{}}();子goroutine panic需各自recover;返回值需类型断言;事务回滚须手动实现且保证幂等;recover后应立即返回而非继续业务逻辑。

panic 之后如何用 recover 捕获并恢复执行
recover 只能在 defer 函数中生效,且仅对当前 goroutine 的 panic 有效。它不是“全局错误处理器”,也不能在任意位置调用就起作用。
常见错误是把 recover() 写在普通函数里,或者没包在 defer 中,结果返回 nil,程序仍崩溃。
- 必须写成
defer func() { if r := recover(); r != nil { /* 处理 */ } }() - 如果 panic 发生在子 goroutine,主 goroutine 的 recover 完全无效,需在子 goroutine 内部单独 defer+recover
-
recover()返回的是interface{},通常要类型断言,比如r.(error)或r.(string),否则可能 panic 二次
事务性操作失败时怎么安全回滚(如 DB + 文件写入)
Go 标准库不提供内置事务抽象,回滚必须手动编码。关键不是“有没有 rollback 方法”,而是“状态是否可逆”和“回滚逻辑是否幂等”。
例如:先写数据库再写本地文件,若文件写入失败,仅靠 recover 无法撤回已提交的 DB 事务——必须用显式事务控制 + 补偿逻辑。
立即学习“go语言免费学习笔记(深入)”;
- DB 层用
tx, err := db.Begin(),出错时调用tx.Rollback();成功才tx.Commit() - 涉及多系统(如 DB + Redis + 文件),推荐“正向执行 + 补偿任务”模式:记录操作日志(
op_log表),失败后异步重试或反向操作 - 避免在 defer 中做回滚(如
defer tx.Rollback()),除非你确定一定需要回滚——它会覆盖Commit()调用
recover 后能否继续正常处理请求(HTTP handler 场景)
可以,但要注意:recover 只阻止 panic 传播,不修复已损坏的状态。HTTP handler 中 recover 后返回 500 是安全的,但直接复用被 panic 中断的 struct 字段可能引发后续 panic。
典型反模式:在 handler 里修改了某个全局计数器或缓存 map,中途 panic,recover 后又继续用这个 map —— 此时 map 可能处于中间态(如部分字段已赋值、锁未释放)。
- recover 后建议立即返回响应,不要继续业务逻辑分支
- 若需降级处理(如 fallback 到缓存数据),应在 panic 前通过
if err != nil显式判断,而非依赖 recover - net/http 默认 panic 会终止连接,所以务必在 middleware 或 handler 入口 wrap 一层 defer-recover
为什么 errors.Is 和 errors.As 比直接 == 更适合错误分类处理
因为 Go 的 error 是接口,不同包返回的 error 实例即使内容相同,== 也会失败。比如 os.Open 和自定义 error 都含 "file not found",但底层指针不同。
errors.Is 利用 Unwrap() 链向上匹配目标 error 值,errors.As 则尝试类型断言到具体 error 类型——这才是面向错误行为(而非字符串)编程的基础。
- 用
errors.Is(err, os.ErrNotExist)判断文件不存在,而不是strings.Contains(err.Error(), "no such file") - 自定义 error 必须实现
Unwrap() error才能参与Is/As链式匹配 - HTTP 错误码映射、重试策略、日志分级都应基于 error 类型或哨兵值,而非字符串解析
实际中最容易被忽略的是:recover 不是错误处理,只是 panic 的逃生舱门;真正的健壮性来自前期防御(校验、超时、资源预占)和失败隔离(goroutine 独立、状态无共享)。回滚逻辑一旦写错,比 panic 更难排查。










