go-error-handling
Use when writing Go code that returns, wraps, or handles errors — choosing between sentinel errors, custom types, and fmt.Errorf (%w vs %v), structuring error flow, or deciding whether to log or return. Also use when propagating errors across package boundaries or using errors.Is/As, even if the use
Install
mkdir -p .claude/skills/go-error-handling-rain-kl && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/13962" && unzip -o skill.zip -d .claude/skills/go-error-handling-rain-kl && rm skill.zipInstalls to .claude/skills/go-error-handling-rain-kl
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
Use when writing Go code that returns, wraps, or handles errors — choosing between sentinel errors, custom types, and fmt.Errorf (%w vs %v), structuring error flow, or deciding whether to log or return. Also use when propagating errors across package boundaries or using errors.Is/As, even if the user doesn't ask about error strategy. Does not cover panic/recover patterns (see go-defensive).About this skill
Go 错误处理
可用脚本
scripts/check-errors.sh— 检测错误处理反模式:对err.Error()进行字符串比较、没有上下文的裸return err、以及日志并返回违规。运行bash scripts/check-errors.sh --help查看选项。
在 Go 中,错误是值 — 它们由代码创建,也由代码消费。
选择错误策略
- 系统边界(RPC、IPC、存储)?→ 使用
%v包装以避免泄露内部细节 - 调用者需要匹配特定条件?→ 哨兵或类型化错误,使用
%w包装 - 调用者只需要调试上下文?→
fmt.Errorf("...: %w", err) - 叶子函数,无需包装?→ 直接返回错误
默认:使用 %w 包装,并将其放在格式字符串的末尾。
核心规则
永不返回具体错误类型
永不从导出函数返回具体错误类型 — 具体的 nil 指针可能变成非 nil 接口:
// 不好:具体类型可能导致微妙的 bug
func Bad() *os.PathError { /*...*/ }
// 好:始终返回 error 接口
func Good() error { /*...*/ }
错误字符串
错误字符串不应大写,也不应以标点符号结尾。例外:导出名称、专有名词或缩写。
// 不好
err := fmt.Errorf("Something bad happened.")
// 好
err := fmt.Errorf("something bad happened")
对于显示的消息(日志、测试失败、API 响应),大写是适当的。
出错时的返回值
当函数返回错误时,调用者必须将所有非错误返回值视为未指定,除非有明确文档说明。
提示:接受 context.Context 的函数通常应返回 error,以便调用者判断上下文是否被取消。
处理错误
遇到错误时,做出深思熟虑的选择 — 不要用 _ 丢弃:
- 立即处理 — 解决错误并继续
- 返回给调用者 — 可选择用上下文包装
- 在特殊情况下 —
log.Fatal或panic
有意忽略时:添加注释说明原因。
n, _ := b.Write(p) // 永不返回非 nil 错误
对于相关的并发操作,使用 errgroup:
g, ctx := errgroup.WithContext(ctx)
g.Go(func() error { return task1(ctx) })
g.Go(func() error { return task2(ctx) })
if err := g.Wait(); err != nil { return err }
避免带内错误
不要返回 -1、nil 或空字符串来表示错误。使用多返回值:
// 不好:带内错误值
func Lookup(key string) int // 缺失时返回 -1
// 好:显式的 error 或 ok 值
func Lookup(key string) (string, bool)
这可以防止调用者写出 Parse(Lookup(key)) — 它会导致编译时错误,因为 Lookup(key) 有 2 个输出。
错误流程
在正常代码之前处理错误。提前返回使正常路径保持无缩进:
// 好:错误优先,正常代码无缩进
if err != nil {
return err
}
// 正常代码
错误只处理一次 — 记录日志或返回,不要两者都做:
遇到错误?
├─ 调用者可以采取行动?→ 返回(通过 %w 附带上下文)
├─ 在调用链顶部?→ 记录日志并处理
└─ 都不是?→ 以适当级别记录日志,继续执行
在组织复杂的错误流程、决定记录日志还是返回、实现一次处理模式、或选择结构化日志级别时,请阅读 references/ERROR-FLOW.md。
错误类型
建议:推荐的最佳实践。
| 调用者需要匹配? | 消息类型 | 使用方式 |
|---|---|---|
| 否 | 静态 | errors.New("message") |
| 否 | 动态 | fmt.Errorf("msg: %v", val) |
| 是 | 静态 | var ErrFoo = errors.New("...") |
| 是 | 动态 | 自定义 error 类型 |
默认:使用 fmt.Errorf("...: %w", err) 包装。升级为哨兵以使用 errors.Is(),升级为自定义类型以使用 errors.As()。
在定义哨兵错误、创建自定义错误类型、或为包 API 选择错误策略时,请阅读 references/ERROR-TYPES.md。
错误包装
建议:推荐的最佳实践。
- 使用
%v:在系统边界、用于日志记录、隐藏内部细节 - 使用
%w:保留错误链以供errors.Is/errors.As使用
关键规则:将 %w 放在末尾。添加调用者没有的上下文。如果注释没有增加信息,直接返回 err。
在决定使用 %v 还是 %w、跨包边界包装错误、或添加上下文信息时,请阅读 references/WRAPPING.md。
验证:实现错误处理后,运行
bash scripts/check-errors.sh检测常见的反模式。然后运行go vet ./...捕获其他问题。
相关技能
- 错误命名:在命名哨兵错误(
ErrFoo)或自定义错误类型时,参见 go-naming - 测试错误:在使用
errors.Is/errors.As测试错误语义或编写错误检查辅助函数时,参见 go-testing - Panic 处理:在决定 panic 还是返回错误、或编写 recover 守卫时,参见 go-defensive
- 守卫子句:在组织提前返回的错误流程或减少嵌套时,参见 go-control-flow
- 日志决策:在选择日志级别、配置结构化日志、或决定日志消息中包含什么上下文时,参见 go-logging