Back to list
dkoosis

go-error-patterns

by dkoosis

0🍴 0📅 Jan 3, 2026

SKILL.md


Go Error Patterns

Idiomatic Go error handling patterns for lint compliance and maintainability.

Overview

Three linters enforce Go error hygiene:

LinterDetectsSolution
err113errors.New() in return statementsExtract to sentinel errors
wrapcheckUnwrapped errors from external packagesWrap with fmt.Errorf("ctx: %w", err)
errorlinterr == ErrFoo comparisonsUse errors.Is(err, ErrFoo)

Pattern 1: Sentinel Errors (err113)

Dynamic errors prevent callers from checking error types. Extract to package-level sentinels.

Problem

func Validate(s string) error {
    if s == "" {
        return errors.New("input is empty")  // err113: dynamic error
    }
    if len(s) > 100 {
        return errors.New("input too long")  // err113: dynamic error
    }
    return nil
}

Solution

var (
    ErrEmptyInput = errors.New("input is empty")
    ErrInputTooLong = errors.New("input too long")
)

func Validate(s string) error {
    if s == "" {
        return ErrEmptyInput
    }
    if len(s) > 100 {
        return ErrInputTooLong
    }
    return nil
}

Naming Conventions

  • Exported sentinels: ErrFoo for errors callers should check
  • Unexported sentinels: errFoo for internal errors
  • Semantic names: ErrNotFound, not ErrError1

When to Use Dynamic Errors

Dynamic errors are acceptable when:

  • The message is unique and caller won't check type
  • Used with fmt.Errorf wrapping: fmt.Errorf("failed at %s: %w", loc, err)

Add //nolint:err113 with justification for intentional dynamic errors.

Pattern 2: Error Wrapping (wrapcheck)

External package errors must be wrapped to provide context for debugging.

Problem

func LoadUser(id string) (*User, error) {
    row := db.QueryRow("SELECT * FROM users WHERE id = ?", id)
    var u User
    if err := row.Scan(&u.ID, &u.Name); err != nil {
        return nil, err  // wrapcheck: sql error not wrapped
    }
    return &u, nil
}

Solution

func LoadUser(id string) (*User, error) {
    row := db.QueryRow("SELECT * FROM users WHERE id = ?", id)
    var u User
    if err := row.Scan(&u.ID, &u.Name); err != nil {
        return nil, fmt.Errorf("load user %s: %w", id, err)
    }
    return &u, nil
}

Wrapping Guidelines

  1. Context: Include what operation failed
  2. Parameters: Include relevant IDs/paths (sanitized)
  3. Verb form: Use present participle: "loading", "parsing", "connecting"
  4. No duplication: Don't repeat what lower layers already say

Good:

return fmt.Errorf("parse config file: %w", err)

Bad:

return fmt.Errorf("error: %w", err)  // Redundant "error"
return fmt.Errorf("parse config file: parse config file: %w", err)  // Duplicated

When NOT to Wrap

  • Same package errors: Already have context
  • Sentinel returns: return ErrNotFound is already descriptive
  • Error transformation: When converting to a different error type

Pattern 3: Error Comparison (errorlint)

Direct comparison breaks with wrapped errors. Use errors.Is() and errors.As().

Problem

if err == sql.ErrNoRows {  // errorlint: won't match wrapped error
    return nil, ErrNotFound
}

Solution

if errors.Is(err, sql.ErrNoRows) {
    return nil, ErrNotFound
}

errors.Is vs errors.As

// errors.Is: Check if error chain contains specific error
if errors.Is(err, os.ErrNotExist) {
    // Handle missing file
}

// errors.As: Extract specific error type from chain
var pathErr *os.PathError
if errors.As(err, &pathErr) {
    fmt.Printf("failed path: %s\n", pathErr.Path)
}

Type Assertion Migration

Before:

if e, ok := err.(*os.PathError); ok {
    // ...
}

After:

var e *os.PathError
if errors.As(err, &e) {
    // ...
}

Quick Reference

ViolationPatternFix
errors.New("msg") in returnerr113Extract to var ErrFoo = errors.New("msg")
return externalPkg.Func() errorwrapcheckfmt.Errorf("ctx: %w", err)
err == ErrFooerrorlinterrors.Is(err, ErrFoo)
err.(*Type)errorlinterrors.As(err, &target)

Verification

After fixes:

golangci-lint run --enable err113,wrapcheck,errorlint ./...
go test ./...

Score

Total Score

35/100

Based on repository quality metrics

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

0/10
説明文

100文字以上の説明がある

0/10
人気

GitHub Stars 100以上

0/15
最近の活動

3ヶ月以内に更新がある

0/10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

0/5
タグ

1つ以上のタグが設定されている

0/5

Reviews

💬

Reviews coming soon