go-error-handling
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseGo Error Handling
Go 错误处理
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.
在Go语言中,错误是值——它们由代码创建并被代码消费。本内容涵盖了如何高效地返回、结构化、包装和处理错误。
Returning Errors
返回错误
Normative: Required per Google's canonical Go style guide.
规范要求:需遵循Google官方Go语言风格指南。
Use the error
Type
error使用error
类型
errorUse to signal that a function can fail. By convention, is the
last result parameter.
errorerrorgo
// 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
pointer can become a non-nil interface value:
nilgo
// Bad: Concrete error type can cause subtle bugs
func Bad() *os.PathError { /*...*/ }
// Good: Always return the error interface
func Good() error { /*...*/ }使用类型来标识函数可能执行失败。按照约定,是函数的最后一个返回参数。
errorerrorgo
// Good:
func Good() error { /* ... */ }
func GoodLookup() (*Result, error) {
// ...
if err != nil {
return nil, err
}
return res, nil
}切勿从导出函数返回具体的错误类型——具体的指针可能会变成非nil的接口值:
nilgo
// Bad: Concrete error type can cause subtle bugs
func Bad() *os.PathError { /*...*/ }
// Good: Always return the error interface
func Good() error { /*...*/ }Return Values on 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 should usually return an
so callers can determine if the context was cancelled.
context.Contexterror当函数返回错误时,调用者必须将所有非错误返回值视为未定义,除非有明确的文档说明。通常,非错误返回值会是其零值。
提示:接收参数的函数通常应返回,以便调用者判断上下文是否已被取消。
context.ContexterrorError Strings
错误字符串
Normative: Required per Google's canonical Go style guide.
Error strings should not be capitalized and should not end with
punctuation:
go
// 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:
go
// 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语言风格指南。
错误字符串不应大写开头,也不应以标点符号结尾:
go
// Bad:
err := fmt.Errorf("Something bad happened.")
// Good:
err := fmt.Errorf("something bad happened")例外情况:如果错误字符串以导出名称、专有名词或首字母缩写开头,则可以大写开头。
理由:错误字符串通常会在其他上下文环境中显示后再被打印出来。
对于显示类消息(日志、测试失败信息、API响应),使用大写开头是合适的:
go
// Good:
log.Infof("Operation aborted: %v", err)
log.Errorf("Operation aborted: %v", err)
t.Errorf("Op(%q) failed unexpectedly; err=%v", args, err)Handling Errors
处理错误
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:
- Handle and address the error immediately
- Return the error to the caller
- In exceptional situations: call or (if absolutely necessary)
log.Fatalpanic
规范要求:需遵循Google官方Go语言风格指南。
遇到错误的代码必须刻意选择处理方式。不要使用变量丢弃错误。
_当函数返回错误时,可选择以下方式之一:
- 立即处理并解决错误
- 将错误返回给调用者
- 在特殊情况下:调用或(在绝对必要时)
log.Fatalpanic
Intentionally Ignoring Errors
刻意忽略错误
In rare cases where ignoring an error is appropriate, add a comment explaining
why:
go
// Good:
var b *bytes.Buffer
n, _ := b.Write(p) // never returns a non-nil error在极少数适合忽略错误的场景下,需添加注释说明原因:
go
// Good:
var b *bytes.Buffer
n, _ := b.Write(p) // never returns a non-nil errorUsing errgroup for Related Operations
使用errgroup处理关联操作
When orchestrating related operations where only the first error is useful,
provides a
convenient abstraction:
errgroupgo
// 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
}当编排仅第一个错误有用的关联操作时,提供了便捷的抽象:
errgroupgo
// 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
}Avoid In-Band Errors
避免带内错误
Normative: Required per Google's canonical Go style guide.
Do not return special values like , , or empty string to signal errors:
-1nilgo
// 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:
go
// 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 - it causes a
compile-time error since has 2 outputs.
Parse(Lookup(key))Lookup(key)规范要求:需遵循Google官方Go语言风格指南。
不要返回像、或空字符串这样的特殊值来表示错误:
-1nilgo
// 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))应使用多返回值替代:
go
// 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)Indent Error Flow
缩进错误流程
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.
go
// Good: Error handling first, normal code unindented
if err != nil {
// error handling
return // or continue, etc.
}
// normal codego
// Bad: Normal code hidden in else clause
if err != nil {
// error handling
} else {
// normal code that looks abnormal due to indentation
}规范要求:需遵循Google官方Go语言风格指南。
在执行正常代码之前先处理错误。这可以提高可读性,让读者快速找到正常执行路径。
go
// Good: Error handling first, normal code unindented
if err != nil {
// error handling
return // or continue, etc.
}
// normal codego
// Bad: Normal code hidden in else clause
if err != nil {
// error handling
} else {
// normal code that looks abnormal due to indentation
}Avoid If-with-Initializer for Long-Lived Variables
避免对长生命周期变量使用带初始化的If语句
If you use a variable for more than a few lines, move the declaration out:
go
// Good: Declaration separate from error check
x, err := f()
if err != nil {
return err
}
// lots of code that uses x
// across multiple linesgo
// 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
}如果变量的使用超过几行代码,应将声明移到外部:
go
// Good: Declaration separate from error check
x, err := f()
if err != nil {
return err
}
// lots of code that uses x
// across multiple linesgo
// 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
}Error Types
错误类型
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 | |
| No | dynamic | |
| Yes | static | |
| Yes | dynamic | custom |
For detailed coverage of sentinel errors, structured error types, and error
checking patterns, see references/ERROR-TYPES.md.
建议:推荐的最佳实践。
如果调用者需要以编程方式区分不同的错误情况,应为错误添加结构,而不是依赖字符串匹配。根据调用者是否需要匹配错误以及消息是静态还是动态来选择合适的错误类型。
快速决策表:
| 调用者是否需要匹配? | 消息类型 | 使用方式 |
|---|---|---|
| 否 | 静态 | |
| 否 | 动态 | |
| 是 | 静态 | |
| 是 | 动态 | 自定义 |
关于哨兵错误、结构化错误类型和错误检查模式的详细内容,请参阅references/ERROR-TYPES.md。
Error Wrapping
错误包装
Advisory: Recommended best practice.
The choice between and significantly impacts how errors are propagated
and inspected:
%v%w- Use : At system boundaries, for logging, to hide internal details
%v - Use : To preserve error chain for
%w/errors.Isinspectionerrors.As
Key rules:
- Place at the end:
%w"context message: %w" - Add context that callers don't have; don't duplicate existing info
- If annotation adds nothing, just return directly
err
For detailed coverage of wrapping patterns, placement, adding context, and
logging best practices, see references/WRAPPING.md.
建议:推荐的最佳实践。
%v%w- 使用:在系统边界、日志记录场景中使用,用于隐藏内部细节
%v - 使用:保留错误链,以便
%w/errors.Is检查errors.As
核心规则:
- 将放在末尾:
%w"context message: %w" - 添加调用者不知道的上下文信息;不要重复已有内容
- 如果注释没有添加任何信息,直接返回即可
err
关于包装模式、放置位置、添加上下文和日志最佳实践的详细内容,请参阅references/WRAPPING.md。
Handle Errors Once
单次处理错误
Source: Uber Go Style Guide
When a caller receives an error, it should handle each error only once.
Choose ONE response:
- Return the error (wrapped or verbatim) for the caller to handle
- Log and degrade gracefully (don't return the error)
- Match and handle specific error cases, return others
Never log AND return - this causes duplicate logging as callers up the stack
will also handle the error.
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)
}
}来源:Uber Go风格指南
当调用者收到错误时,应仅处理一次每个错误。选择以下一种处理方式:
- 返回错误(包装或原样返回),由调用者处理
- 记录日志并优雅降级(不返回错误)
- 匹配并处理特定错误情况,返回其他错误
切勿同时记录日志和返回错误——这会导致日志重复,因为调用栈上层的代码也会处理该错误。
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)
}
}Quick Reference
快速参考
| Pattern | Guidance |
|---|---|
| Return type | Always use |
| 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 |
| %v vs %w | |
| %w placement | Always at the end: |
| Handle once | Choose ONE: return, log+degrade, or match+handle |
| Logging | Don't log and return; let caller decide |
| 模式 | 指导原则 |
|---|---|
| 返回类型 | 始终使用 |
| 错误字符串 | 小写开头,无标点符号 |
| 忽略错误 | 添加注释说明安全原因 |
| 带内错误 | 避免使用;使用多返回值 |
| 错误流程 | 先处理错误,不使用else分支 |
| 错误类型选择 | 需要匹配+动态→自定义类型;静态→哨兵错误 |
| 哨兵错误 | 使用 |
| %v vs %w | |
| %w放置位置 | 始终放在末尾: |
| 单次处理 | 选择一种方式:返回、记录+降级、匹配+处理 |
| 日志记录 | 不要同时记录和返回;让调用者决定 |
See Also
另请参阅
- go-style-core: Core Go style principles and formatting
- go-naming: Naming conventions including error naming (ErrFoo)
- go-testing: Testing patterns including error testing
- go-defensive: Defensive programming including panic handling
- go-linting: Linting tools that catch error handling issues
- go-style-core:核心Go语言风格原则和格式化规范
- go-naming:命名约定,包括错误命名(ErrFoo)
- go-testing:测试模式,包括错误测试
- go-defensive:防御性编程,包括panic处理
- go-linting:用于捕获错误处理问题的代码检查工具
Reference Files
参考文件
- references/ERROR-TYPES.md - Sentinel errors, structured error types, choosing error types, and checking errors
- references/WRAPPING.md - Error wrapping with %v vs %w, placement, adding context, and logging
- references/ERROR-TYPES.md - 哨兵错误、结构化错误类型、错误类型选择和错误检查
- references/WRAPPING.md - 使用%v vs %w进行错误包装、放置位置、添加上下文和日志记录