error-handling
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseError Handling
错误处理
Implement idiomatic Go error handling patterns.
实现符合Go语言惯用风格的错误处理模式。
Quick Start
快速入门
Basic error handling:
go
result, err := doSomething()
if err != nil {
return fmt.Errorf("do something: %w", err)
}Sentinel error:
go
var ErrNotFound = errors.New("not found")
if errors.Is(err, ErrNotFound) {
// Handle not found
}基础错误处理:
go
result, err := doSomething()
if err != nil {
return fmt.Errorf("do something: %w", err)
}哨兵错误:
go
var ErrNotFound = errors.New("not found")
if errors.Is(err, ErrNotFound) {
// Handle not found
}Instructions
操作步骤
Step 1: Return Errors
步骤1:返回错误
Basic error return:
go
func ReadFile(path string) ([]byte, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("read file %s: %w", path, err)
}
return data, nil
}Multiple return values:
go
func Divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}基础错误返回:
go
func ReadFile(path string) ([]byte, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("read file %s: %w", path, err)
}
return data, nil
}多返回值:
go
func Divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}Step 2: Wrap Errors with Context
步骤2:添加上下文包装错误
Using fmt.Errorf with %w:
go
func ProcessFile(path string) error {
data, err := ReadFile(path)
if err != nil {
return fmt.Errorf("process file: %w", err)
}
if err := Validate(data); err != nil {
return fmt.Errorf("validate data: %w", err)
}
return nil
}Error chain:
go
// Original error
err := os.Open("file.txt")
// Wrapped once
err = fmt.Errorf("open config: %w", err)
// Wrapped again
err = fmt.Errorf("initialize app: %w", err)
// Unwrap to check original
if errors.Is(err, os.ErrNotExist) {
// Handle file not found
}使用带%w的fmt.Errorf:
go
func ProcessFile(path string) error {
data, err := ReadFile(path)
if err != nil {
return fmt.Errorf("process file: %w", err)
}
if err := Validate(data); err != nil {
return fmt.Errorf("validate data: %w", err)
}
return nil
}错误链:
go
// 原始错误
err := os.Open("file.txt")
// 第一次包装
err = fmt.Errorf("open config: %w", err)
// 再次包装
err = fmt.Errorf("initialize app: %w", err)
// 解包检查原始错误
if errors.Is(err, os.ErrNotExist) {
// 处理文件不存在情况
}Step 3: Use Sentinel Errors
步骤3:使用哨兵错误
Define sentinel errors:
go
var (
ErrNotFound = errors.New("not found")
ErrUnauthorized = errors.New("unauthorized")
ErrInvalidInput = errors.New("invalid input")
)
func GetUser(id string) (*User, error) {
user, ok := cache[id]
if !ok {
return nil, ErrNotFound
}
return user, nil
}Check with errors.Is:
go
user, err := GetUser("123")
if errors.Is(err, ErrNotFound) {
// Handle not found case
return nil
}
if err != nil {
// Handle other errors
return err
}定义哨兵错误:
go
var (
ErrNotFound = errors.New("not found")
ErrUnauthorized = errors.New("unauthorized")
ErrInvalidInput = errors.New("invalid input")
)
func GetUser(id string) (*User, error) {
user, ok := cache[id]
if !ok {
return nil, ErrNotFound
}
return user, nil
}使用errors.Is检查:
go
user, err := GetUser("123")
if errors.Is(err, ErrNotFound) {
// 处理资源不存在情况
return nil
}
if err != nil {
// 处理其他错误
return err
}Step 4: Create Custom Error Types
步骤4:创建自定义错误类型
Error type with fields:
go
type ValidationError struct {
Field string
Value interface{}
Msg string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("validation failed for %s: %s", e.Field, e.Msg)
}
func Validate(user *User) error {
if user.Email == "" {
return &ValidationError{
Field: "email",
Value: user.Email,
Msg: "email is required",
}
}
return nil
}Check with errors.As:
go
if err := Validate(user); err != nil {
var valErr *ValidationError
if errors.As(err, &valErr) {
fmt.Printf("Field %s failed: %s\n", valErr.Field, valErr.Msg)
}
return err
}带字段的错误类型:
go
type ValidationError struct {
Field string
Value interface{}
Msg string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("validation failed for %s: %s", e.Field, e.Msg)
}
func Validate(user *User) error {
if user.Email == "" {
return &ValidationError{
Field: "email",
Value: user.Email,
Msg: "email is required",
}
}
return nil
}使用errors.As检查:
go
if err := Validate(user); err != nil {
var valErr *ValidationError
if errors.As(err, &valErr) {
fmt.Printf("Field %s failed: %s\n", valErr.Field, valErr.Msg)
}
return err
}Step 5: Handle Errors Appropriately
步骤5:正确处理错误
Check immediately:
go
// Good
result, err := doSomething()
if err != nil {
return err
}
// Bad - don't defer error checking
result, err := doSomething()
// ... more code ...
if err != nil {
return err
}Don't ignore errors:
go
// Bad
doSomething()
// Good
if err := doSomething(); err != nil {
log.Printf("warning: %v", err)
}
// Or explicitly ignore
_ = doSomething()立即检查错误:
go
// 推荐写法
result, err := doSomething()
if err != nil {
return err
}
// 不推荐 - 不要延迟错误检查
result, err := doSomething()
// ... 更多代码 ...
if err != nil {
return err
}不要忽略错误:
go
// 不推荐
doSomething()
// 推荐写法
if err := doSomething(); err != nil {
log.Printf("warning: %v", err)
}
// 或显式忽略
_ = doSomething()Common Patterns
常见模式
Error Wrapping Chain
错误包装链
go
func LoadConfig() (*Config, error) {
data, err := readConfigFile()
if err != nil {
return nil, fmt.Errorf("load config: %w", err)
}
config, err := parseConfig(data)
if err != nil {
return nil, fmt.Errorf("load config: %w", err)
}
return config, nil
}go
func LoadConfig() (*Config, error) {
data, err := readConfigFile()
if err != nil {
return nil, fmt.Errorf("load config: %w", err)
}
config, err := parseConfig(data)
if err != nil {
return nil, fmt.Errorf("load config: %w", err)
}
return config, nil
}Multiple Error Returns
多错误返回
go
func ProcessBatch(items []Item) ([]Result, error) {
results := make([]Result, 0, len(items))
for _, item := range items {
result, err := process(item)
if err != nil {
return nil, fmt.Errorf("process item %s: %w", item.ID, err)
}
results = append(results, result)
}
return results, nil
}go
func ProcessBatch(items []Item) ([]Result, error) {
results := make([]Result, 0, len(items))
for _, item := range items {
result, err := process(item)
if err != nil {
return nil, fmt.Errorf("process item %s: %w", item.ID, err)
}
results = append(results, result)
}
return results, nil
}Collecting Multiple Errors
收集多个错误
go
type MultiError []error
func (m MultiError) Error() string {
var msgs []string
for _, err := range m {
msgs = append(msgs, err.Error())
}
return strings.Join(msgs, "; ")
}
func ValidateAll(users []*User) error {
var errs MultiError
for _, user := range users {
if err := Validate(user); err != nil {
errs = append(errs, err)
}
}
if len(errs) > 0 {
return errs
}
return nil
}go
type MultiError []error
func (m MultiError) Error() string {
var msgs []string
for _, err := range m {
msgs = append(msgs, err.Error())
}
return strings.Join(msgs, "; ")
}
func ValidateAll(users []*User) error {
var errs MultiError
for _, user := range users {
if err := Validate(user); err != nil {
errs = append(errs, err)
}
}
if len(errs) > 0 {
return errs
}
return nil
}Panic and Recover
Panic与Recover
Use panic for programmer errors:
go
func MustCompile(pattern string) *regexp.Regexp {
re, err := regexp.Compile(pattern)
if err != nil {
panic(err) // Invalid pattern is programmer error
}
return re
}Recover from panics:
go
func SafeExecute(fn func()) (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("panic: %v", r)
}
}()
fn()
return nil
}Panic用于程序员错误:
go
func MustCompile(pattern string) *regexp.Regexp {
re, err := regexp.Compile(pattern)
if err != nil {
panic(err) // 无效正则属于程序员错误
}
return re
}从Panic中恢复:
go
func SafeExecute(fn func()) (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("panic: %v", r)
}
}()
fn()
return nil
}Error Context
错误上下文
go
type contextError struct {
op string
err error
}
func (e *contextError) Error() string {
return fmt.Sprintf("%s: %v", e.op, e.err)
}
func (e *contextError) Unwrap() error {
return e.err
}
func withContext(op string, err error) error {
if err == nil {
return nil
}
return &contextError{op: op, err: err}
}go
type contextError struct {
op string
err error
}
func (e *contextError) Error() string {
return fmt.Sprintf("%s: %v", e.op, e.err)
}
func (e *contextError) Unwrap() error {
return e.err
}
func withContext(op string, err error) error {
if err == nil {
return nil
}
return &contextError{op: op, err: err}
}Error Handling in HTTP
HTTP场景下的错误处理
go
func handler(w http.ResponseWriter, r *http.Request) {
user, err := getUser(r.Context(), r.URL.Query().Get("id"))
if errors.Is(err, ErrNotFound) {
http.Error(w, "User not found", http.StatusNotFound)
return
}
if errors.Is(err, ErrUnauthorized) {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
if err != nil {
log.Printf("get user: %v", err)
http.Error(w, "Internal error", http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(user)
}go
func handler(w http.ResponseWriter, r *http.Request) {
user, err := getUser(r.Context(), r.URL.Query().Get("id"))
if errors.Is(err, ErrNotFound) {
http.Error(w, "User not found", http.StatusNotFound)
return
}
if errors.Is(err, ErrUnauthorized) {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
if err != nil {
log.Printf("get user: %v", err)
http.Error(w, "Internal error", http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(user)
}Error Handling in Goroutines
Goroutine中的错误处理
go
func processAsync(items []Item) error {
errCh := make(chan error, len(items))
for _, item := range items {
go func(it Item) {
errCh <- process(it)
}(item)
}
// Collect errors
for range items {
if err := <-errCh; err != nil {
return fmt.Errorf("process failed: %w", err)
}
}
return nil
}go
func processAsync(items []Item) error {
errCh := make(chan error, len(items))
for _, item := range items {
go func(it Item) {
errCh <- process(it)
}(item)
}
// 收集错误
for range items {
if err := <-errCh; err != nil {
return fmt.Errorf("process failed: %w", err)
}
}
return nil
}Best Practices
最佳实践
- Return errors, don't panic: Except for programmer errors
- Check errors immediately: Don't defer checking
- Wrap errors with context: Use fmt.Errorf with %w
- Use sentinel errors: For expected error cases
- Use custom types: For errors needing additional data
- errors.Is for sentinel: Check wrapped sentinel errors
- errors.As for types: Extract custom error types
- Don't ignore errors: Handle or explicitly ignore with _
- Error messages lowercase: Start with lowercase, no punctuation
- Add context: Include operation and relevant data
- 返回错误,而非Panic:除非是程序员错误
- 立即检查错误:不要延迟检查
- 添加上下文包装错误:使用带%w的fmt.Errorf
- 使用哨兵错误:针对预期的错误场景
- 使用自定义类型:针对需要附加数据的错误
- 用errors.Is检查哨兵错误:检查被包装的哨兵错误
- 用errors.As提取错误类型:提取自定义错误类型
- 不要忽略错误:处理或用_显式忽略
- 错误消息小写开头:以小写字母开头,无标点符号
- 添加上下文信息:包含操作名称和相关数据
Anti-Patterns
反模式
Don't:
- Ignore errors silently
- Use panic for normal errors
- Return error strings (use error types)
- Check error strings (use errors.Is/As)
- Create errors with fmt.Sprintf (use fmt.Errorf)
- Wrap errors without %w (loses error chain)
请勿:
- 静默忽略错误
- 针对普通错误使用Panic
- 返回错误字符串(使用错误类型)
- 检查错误字符串(使用errors.Is/As)
- 用fmt.Sprintf创建错误(使用fmt.Errorf)
- 不使用%w包装错误(会丢失错误链)
Troubleshooting
问题排查
Error not matching with errors.Is:
- Ensure using %w when wrapping
- Check sentinel error is same instance
Can't extract error type:
- Use errors.As, not type assertion
- Ensure error type is pointer
Lost error context:
- Wrap errors at each level
- Include operation name in wrap
使用errors.Is无法匹配错误:
- 确保包装时使用了%w
- 检查哨兵错误是否为同一实例
无法提取错误类型:
- 使用errors.As,而非类型断言
- 确保错误类型是指针类型
丢失错误上下文:
- 在每个层级包装错误
- 包装时包含操作名称