Go中error是值而非异常,应显式检查而非用panic拦截;panic仅用于致命错误;errors.New和fmt.Errorf需精准传递调试上下文;errors.Is/As用于必要类型判断;HTTP handler中须将error转为恰当响应状态码。

Go 里 error 不是异常,别用 panic 拦住它
Go 的 error 是值,不是控制流机制。很多人一看到 nil 就下意识想“捕获”,结果滥用 panic 或包一层 recover,反而让错误更难定位、日志更混乱。
- 真实场景:HTTP handler 里数据库查询失败,应该返回 500 + 日志,而不是
panic导致整个 goroutine 崩溃 -
panic只该用于程序无法继续的致命状态(比如配置文件缺失且无默认值、初始化失败),不是error的替代品 - 用
if err != nil显式检查,比任何“自动错误处理库”都可靠、易读、可调试
怎么写好 errors.New 和 fmt.Errorf
错误信息不是日志,也不是给用户看的提示,而是给开发者 debug 用的上下文快照。空泛的 "failed" 或带堆栈的长字符串,都会拖慢排查速度。
-
errors.New("invalid ID")适合固定、无参数的简单错误 -
fmt.Errorf("failed to parse timestamp %q: %w", input, err)—— 用%w包裹底层错误,保留原始error链;不用%v或%s吞掉它 - 避免拼接路径、ID 等敏感字段时裸露(比如
fmt.Errorf("open %s: %w", path, err)),可能泄露信息;必要时脱敏或记录到日志而非错误消息中
什么时候该用 errors.Is 和 errors.As
不是所有错误都需要判断类型。只有当你明确要区分「重试able 错误」和「永久失败」,或者需要提取特定结构体字段时,才值得引入这两个函数。
-
errors.Is(err, os.ErrNotExist)判断是否是文件不存在 —— 安全、跨包兼容,比err == os.ErrNotExist更健壮 -
var e *os.PathError; if errors.As(err, &e) { log.Println("path:", e.Path) }提取底层错误字段,但注意:必须传指针,且变量声明在if外(否则作用域不对) - 别为了“看起来专业”而层层包装:如果错误只在本包内流转,直接定义导出的错误变量(
var ErrTimeout = errors.New("timeout"))更轻量、更可控
HTTP handler 里 error 怎么透出又不崩服务
Web 服务最常见错误就是没把 error 转成响应,要么静默吞掉,要么 panic 导致连接中断。核心就一条:每个可能出错的 I/O 调用后,立刻决定它是「可恢复」还是「需上报」。
立即学习“go语言免费学习笔记(深入)”;
- 数据库查询失败 → 记日志 + 返回
http.StatusInternalServerError,不要 return nil - 用户提交了非法 JSON → 用
json.Unmarshal的 error 构造http.StatusBadRequest,附带简短提示(如{"error": "invalid json"}),别暴露原始 error 字符串 - 中间件里统一处理:可以封装一个
handleErr(w http.ResponseWriter, err error),但别试图“自动分类”所有错误——业务语义只能由 handler 自己判断
最难的不是写 if err != nil,而是每次遇到新错误时,想清楚它到底属于哪一层:是调用方该重试,还是下游该修复,还是前端该提示?这个判断没法靠工具,得看上下文。漏掉一次,线上就多一个 mystery crash。


















