go-error-handling by cxuu/golang-skills
npx skills add https://github.com/cxuu/golang-skills --skill go-error-handling在 Go 语言中,错误即值 —— 它们由代码创建,并由代码消费。本技能涵盖了如何有效地返回、构建、包装和处理错误。
规范性:根据 Google 的 Go 规范风格指南要求。
error 类型使用 error 来表明函数可能失败。按照惯例,error 是最后一个结果参数。
// Good:
func Good() error { /* ... */ }
func GoodLookup() (*Result, error) {
// ...
if err != nil {
return nil, err
}
return res, nil
}
切勿从导出的函数返回具体的错误类型 —— 一个具体的 nil 指针可能变成一个非 nil 的接口值:
// Bad: Concrete error type can cause subtle bugs
func Bad() *os.PathError { /*...*/ }
// Good: Always return the error interface
func Good() error { /*...*/ }
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
当一个函数返回错误时,除非有明确文档说明,否则调用者必须将所有非错误返回值视为未指定值。通常,非错误返回值是它们的零值。
提示:接受 context.Context 参数的函数通常应该返回一个 error,以便调用者能够判断上下文是否被取消。
规范性:根据 Google 的 Go 规范风格指南要求。
错误字符串不应大写,并且不应以标点符号结尾:
// Bad:
err := fmt.Errorf("Something bad happened.")
// Good:
err := fmt.Errorf("something bad happened")
例外:如果错误字符串以导出的名称、专有名词或首字母缩略词开头,则可以首字母大写。
理由:错误字符串通常在打印之前出现在其他上下文中。
对于显示的消息(日志、测试失败、API 响应),使用大写是合适的:
// Good:
log.Infof("Operation aborted: %v", err)
log.Errorf("Operation aborted: %v", err)
t.Errorf("Op(%q) failed unexpectedly; err=%v", args, err)
规范性:根据 Google 的 Go 规范风格指南要求。
遇到错误的代码必须慎重选择如何处理它。不要使用 _ 变量丢弃错误。
当一个函数返回错误时,请执行以下操作之一:
log.Fatal 或(如果绝对必要)panic在极少数适合忽略错误的情况下,添加注释说明原因:
// Good:
var b *bytes.Buffer
n, _ := b.Write(p) // never returns a non-nil error
当编排相关操作且只有第一个错误有用时,errgroup 提供了一个便捷的抽象:
// Good: errgroup handles cancellation and first-error semantics
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
}
规范性:根据 Google 的 Go 规范风格指南要求。
不要返回特殊值(如 -1、nil 或空字符串)来指示错误:
// Bad: In-band error value
// Lookup returns the value for key or -1 if there is no mapping for key.
func Lookup(key string) int
// Bad: Caller mistakes can attribute errors to wrong function
return Parse(Lookup(missingKey))
改用多个返回值:
// Good: Explicit error or ok value
func Lookup(key string) (value string, ok bool)
// Good: Forces caller to handle the error case
value, ok := Lookup(key)
if !ok {
return fmt.Errorf("no value for %q", key)
}
return Parse(value)
这样可以防止调用者编写 Parse(Lookup(key)) —— 因为 Lookup(key) 有 2 个输出,所以会导致编译时错误。
规范性:根据 Google 的 Go 规范风格指南要求。
在处理正常代码之前先处理错误。这通过使读者能够快速找到正常路径来提高可读性。
// Good: Error handling first, normal code unindented
if err != nil {
// error handling
return // or continue, etc.
}
// normal code
// Bad: Normal code hidden in else clause
if err != nil {
// error handling
} else {
// normal code that looks abnormal due to indentation
}
如果你要使用一个变量超过几行,请将声明移出:
// Good: Declaration separate from error check
x, err := f()
if err != nil {
return err
}
// lots of code that uses x
// across multiple lines
// Bad: Variable scoped to else block, hard to read
if x, err := f(); err != nil {
return err
} else {
// lots of code that uses x
// across multiple lines
}
建议性:推荐的最佳实践。
如果调用者需要通过编程方式区分不同的错误情况,请为错误提供结构,而不是依赖字符串匹配。根据调用者是否需要匹配错误以及消息是静态还是动态的,选择合适的错误类型。
快速决策表:
| 调用者需要匹配? | 消息类型 | 使用 |
|---|---|---|
| 否 | 静态 | errors.New("message") |
| 否 | 动态 | fmt.Errorf("msg: %v", val) |
| 是 | 静态 | var ErrFoo = errors.New("...") |
| 是 | 动态 | 自定义 error 类型 |
关于哨兵错误、结构化错误类型和错误检查模式的详细内容,请参阅 references/ERROR-TYPES.md。
建议性:推荐的最佳实践。
%v 和 %w 之间的选择会显著影响错误的传播和检查方式:
%v:在系统边界、用于日志记录、隐藏内部细节%w:为 errors.Is/errors.As 检查保留错误链关键规则:
%w 放在末尾:"context message: %w"err 即可关于包装模式、放置位置、添加上下文和日志记录最佳实践的详细内容,请参阅 references/WRAPPING.md。
来源:Uber Go 风格指南
当调用者收到错误时,它应该只处理一次每个错误。选择一种响应方式:
切勿同时记录日志和返回错误 —— 这会导致重复记录日志,因为调用栈上层的调用者也会处理这个错误。
// Bad: Logs AND returns - causes noise in logs
u, err := getUser(id)
if err != nil {
log.Printf("Could not get user %q: %v", id, err)
return err // Callers will also log this!
}
// Good: Wrap and return - let caller decide how to handle
u, err := getUser(id)
if err != nil {
return fmt.Errorf("get user %q: %w", id, err)
}
// Good: Log and degrade gracefully (don't return error)
if err := emitMetrics(); err != nil {
// Failure to write metrics should not break the application
log.Printf("Could not emit metrics: %v", err)
}
// Continue execution...
// Good: Match specific errors, return others
tz, err := getUserTimeZone(id)
if err != nil {
if errors.Is(err, ErrUserNotFound) {
// User doesn't exist. Use UTC.
tz = time.UTC
} else {
return fmt.Errorf("get user %q: %w", id, err)
}
}
| 模式 | 指导原则 |
|---|---|
| 返回类型 | 始终使用 error 接口,而非具体类型 |
| 错误字符串 | 小写,无标点 |
| 忽略错误 | 注释说明为何安全 |
| 带内错误 | 避免;使用多个返回值 |
| 错误流 | 先处理错误,不使用 else 子句 |
| 错误类型选择 | 需要匹配 + 动态 → 自定义类型;静态 → 哨兵 |
| 哨兵错误 | 使用 errors.Is 进行检查 |
| %v 与 %w | %v 用于边界,%w 用于保留链 |
| %w 放置位置 | 始终在末尾:"context: %w" |
| 只处理一次 | 选择一种:返回、记录日志+降级,或匹配+处理 |
| 日志记录 | 不要同时记录日志和返回;让调用者决定 |
每周安装量
157
代码仓库
GitHub 星标数
35
首次出现
2026年1月27日
安全审计
安装于
github-copilot145
codex145
gemini-cli144
opencode144
kimi-cli142
amp142
In Go, errors are values - they are created by code and consumed by code. This skill covers how to return, structure, wrap, and handle errors effectively.
Normative : Required per Google's canonical Go style guide.
error TypeUse error to signal that a function can fail. By convention, error is the last result parameter.
// Good:
func Good() error { /* ... */ }
func GoodLookup() (*Result, error) {
// ...
if err != nil {
return nil, err
}
return res, nil
}
Never return concrete error types from exported functions - a concrete nil pointer can become a non-nil interface value:
// Bad: Concrete error type can cause subtle bugs
func Bad() *os.PathError { /*...*/ }
// Good: Always return the error interface
func Good() error { /*...*/ }
When a function returns an error, callers must treat all non-error return values as unspecified unless explicitly documented. Commonly, non-error return values are their zero values.
Tip : Functions taking a context.Context should usually return an error so callers can determine if the context was cancelled.
Normative : Required per Google's canonical Go style guide.
Error strings should not be capitalized and should not end with punctuation:
// Bad:
err := fmt.Errorf("Something bad happened.")
// Good:
err := fmt.Errorf("something bad happened")
Exception : Error strings may start with a capital letter if they begin with an exported name, proper noun, or acronym.
Rationale : Error strings usually appear within other context before being printed.
For displayed messages (logs, test failures, API responses), capitalization is appropriate:
// Good:
log.Infof("Operation aborted: %v", err)
log.Errorf("Operation aborted: %v", err)
t.Errorf("Op(%q) failed unexpectedly; err=%v", args, err)
Normative : Required per Google's canonical Go style guide.
Code that encounters an error must make a deliberate choice about how to handle it. Do not discard errors using _ variables.
When a function returns an error, do one of:
log.Fatal or (if absolutely necessary) panicIn rare cases where ignoring an error is appropriate, add a comment explaining why:
// Good:
var b *bytes.Buffer
n, _ := b.Write(p) // never returns a non-nil error
When orchestrating related operations where only the first error is useful, errgroup provides a convenient abstraction:
// Good: errgroup handles cancellation and first-error semantics
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
}
Normative : Required per Google's canonical Go style guide.
Do not return special values like -1, nil, or empty string to signal errors:
// Bad: In-band error value
// Lookup returns the value for key or -1 if there is no mapping for key.
func Lookup(key string) int
// Bad: Caller mistakes can attribute errors to wrong function
return Parse(Lookup(missingKey))
Use multiple return values instead:
// Good: Explicit error or ok value
func Lookup(key string) (value string, ok bool)
// Good: Forces caller to handle the error case
value, ok := Lookup(key)
if !ok {
return fmt.Errorf("no value for %q", key)
}
return Parse(value)
This prevents callers from writing Parse(Lookup(key)) - it causes a compile-time error since Lookup(key) has 2 outputs.
Normative : Required per Google's canonical Go style guide.
Handle errors before proceeding with normal code. This improves readability by enabling the reader to find the normal path quickly.
// Good: Error handling first, normal code unindented
if err != nil {
// error handling
return // or continue, etc.
}
// normal code
// Bad: Normal code hidden in else clause
if err != nil {
// error handling
} else {
// normal code that looks abnormal due to indentation
}
If you use a variable for more than a few lines, move the declaration out:
// Good: Declaration separate from error check
x, err := f()
if err != nil {
return err
}
// lots of code that uses x
// across multiple lines
// Bad: Variable scoped to else block, hard to read
if x, err := f(); err != nil {
return err
} else {
// lots of code that uses x
// across multiple lines
}
Advisory : Recommended best practice.
If callers need to distinguish different error conditions programmatically, give errors structure rather than relying on string matching. Choose the right error type based on whether callers need to match errors and whether messages are static or dynamic.
Quick decision table :
| Caller needs to match? | Message type | Use |
|---|---|---|
| No | static | errors.New("message") |
| No | dynamic | fmt.Errorf("msg: %v", val) |
| Yes | static | var ErrFoo = errors.New("...") |
| Yes | dynamic | custom error type |
For detailed coverage of sentinel errors, structured error types, and error checking patterns, see references/ERROR-TYPES.md.
Advisory : Recommended best practice.
The choice between %v and %w significantly impacts how errors are propagated and inspected:
%v: At system boundaries, for logging, to hide internal details%w: To preserve error chain for errors.Is/errors.As inspectionKey rules :
%w at the end : "context message: %w"err directlyFor detailed coverage of wrapping patterns, placement, adding context, and logging best practices, see references/WRAPPING.md.
Source : Uber Go Style Guide
When a caller receives an error, it should handle each error only once. Choose ONE response:
Never log AND return - this causes duplicate logging as callers up the stack will also handle the error.
// Bad: Logs AND returns - causes noise in logs
u, err := getUser(id)
if err != nil {
log.Printf("Could not get user %q: %v", id, err)
return err // Callers will also log this!
}
// Good: Wrap and return - let caller decide how to handle
u, err := getUser(id)
if err != nil {
return fmt.Errorf("get user %q: %w", id, err)
}
// Good: Log and degrade gracefully (don't return error)
if err := emitMetrics(); err != nil {
// Failure to write metrics should not break the application
log.Printf("Could not emit metrics: %v", err)
}
// Continue execution...
// Good: Match specific errors, return others
tz, err := getUserTimeZone(id)
if err != nil {
if errors.Is(err, ErrUserNotFound) {
// User doesn't exist. Use UTC.
tz = time.UTC
} else {
return fmt.Errorf("get user %q: %w", id, err)
}
}
| Pattern | Guidance |
|---|---|
| Return type | Always use error interface, not concrete types |
| Error strings | Lowercase, no punctuation |
| Ignoring errors | Comment explaining why it's safe |
| In-band errors | Avoid; use multiple returns |
| Error flow | Handle errors first, no else clauses |
| Error type choice | Match needed + dynamic → custom type; static → sentinel |
| Sentinel errors | Use errors.Is for checking |
| %v vs %w | %v for boundaries, %w for chain preservation |
Weekly Installs
157
Repository
GitHub Stars
35
First Seen
Jan 27, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
github-copilot145
codex145
gemini-cli144
opencode144
kimi-cli142
amp142
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
106,200 周安装
| %w placement | Always at the end: "context: %w" |
| Handle once | Choose ONE: return, log+degrade, or match+handle |
| Logging | Don't log and return; let caller decide |