home / skills / gopherguides / gopher-ai / go-best-practices

go-best-practices skill

/plugins/go-dev/skills/go-best-practices

This skill helps you write idiomatic Go by applying best practices for error handling, interfaces, concurrency, testing, and package organization.

npx playbooks add skill gopherguides/gopher-ai --skill go-best-practices

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
4.4 KB
---
name: go-best-practices
description: |
  WHEN: User is writing Go code, asking about Go patterns, reviewing Go code, or asking
  questions like "what's the best way to..." in Go projects
  WHEN NOT: Non-Go languages, general questions unrelated to Go programming
---

# Go Best Practices Skill

Apply idiomatic Go patterns and best practices from Gopher Guides training materials.

## When Helping with Go Code

### Error Handling

- **Wrap errors with context**: Use `fmt.Errorf("operation failed: %w", err)`
- **Check errors immediately**: Don't defer error checking
- **Return errors, don't panic**: Panics are for unrecoverable situations only
- **Create sentinel errors** for expected conditions: `var ErrNotFound = errors.New("not found")`
- **Use errors.Is() and errors.As()** for error comparison

```go
// Good
if err != nil {
    return fmt.Errorf("failed to process user %s: %w", userID, err)
}

// Avoid
if err != nil {
    log.Fatal(err)  // Don't panic on recoverable errors
}
```

### Interface Design

- **Accept interfaces, return structs**: Functions should accept interfaces but return concrete types
- **Keep interfaces small**: Prefer single-method interfaces
- **Define interfaces at point of use**: Not where the implementation lives
- **Don't export interfaces unnecessarily**: Only if users need to mock

```go
// Good - interface defined by consumer
type Reader interface {
    Read(p []byte) (n int, err error)
}

func ProcessData(r Reader) error { ... }

// Avoid - exporting implementation details
type Service interface {
    Method1() error
    Method2() error
    Method3() error  // Too many methods
}
```

### Concurrency

- **Don't communicate by sharing memory; share memory by communicating**
- **Use channels for coordination, mutexes for state**
- **Always pass context.Context as first parameter**
- **Use errgroup for coordinating goroutines**
- **Avoid goroutine leaks**: Ensure goroutines can exit

```go
// Good - using errgroup
g, ctx := errgroup.WithContext(ctx)
for _, item := range items {
    item := item  // capture loop variable
    g.Go(func() error {
        return process(ctx, item)
    })
}
if err := g.Wait(); err != nil {
    return err
}
```

### Testing

- **Use table-driven tests** for multiple scenarios
- **Call t.Parallel()** for independent tests
- **Use t.Helper()** in test helpers
- **Test behavior, not implementation**
- **Use testify for assertions** when it improves readability

```go
func TestAdd(t *testing.T) {
    tests := []struct {
        name string
        a, b int
        want int
    }{
        {"positive numbers", 2, 3, 5},
        {"with zero", 5, 0, 5},
        {"negative numbers", -2, -3, -5},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            t.Parallel()
            got := Add(tt.a, tt.b)
            if got != tt.want {
                t.Errorf("Add(%d, %d) = %d, want %d", tt.a, tt.b, got, tt.want)
            }
        })
    }
}
```

### Package Organization

- **Package names should be short and lowercase**: `user` not `userService`
- **Avoid package-level state**: Use dependency injection
- **One package per directory**: No multi-package directories
- **internal/ for non-public packages**: Prevents external imports

### Naming Conventions

- **Use MixedCaps or mixedCaps**: Not underscores
- **Acronyms should be consistent**: `URL`, `HTTP`, `ID` (all caps for exported, all lower otherwise)
- **Short names for short scopes**: `i` for loop index, `err` for errors
- **Descriptive names for exports**: `ReadConfig` not `RC`

### Code Organization

- **Declare variables close to use**
- **Use defer for cleanup immediately after resource acquisition**
- **Group related declarations**
- **Order: constants, variables, types, functions**

## Anti-Patterns to Avoid

- **Empty interface (`interface{}` or `any`)**: Use specific types when possible
- **Global state**: Prefer dependency injection
- **Naked returns**: Always name what you're returning
- **Stuttering**: `user.UserService` should be `user.Service`
- **init() functions**: Prefer explicit initialization
- **Complex constructors**: Use functional options pattern

## When in Doubt

- Refer to [Effective Go](https://go.dev/doc/effective_go)
- Check the Go standard library for examples
- Use `go vet` and `staticcheck` for automated guidance

---

*This skill is powered by Gopher Guides training materials. For comprehensive Go training, visit [gopherguides.com](https://gopherguides.com).*

Overview

This skill provides concise, opinionated Go best practices to apply while writing, reviewing, or refactoring Go code. It focuses on idiomatic patterns for error handling, interfaces, concurrency, testing, package layout, naming, and code organization. Use it to get actionable recommendations that improve maintainability, correctness, and readability.

How this skill works

When given Go code or a design question, the skill inspects code structure and intent and returns targeted guidance based on Go idioms. It highlights concrete fixes (error wrapping, interface placement, context usage, test structure), points out anti-patterns, and suggests tooling and references for verification. Recommendations are practical and include small code-pattern examples you can apply immediately.

When to use it

  • Writing new Go packages or APIs and choosing types/interfaces
  • Reviewing or refactoring existing Go code for idiomatic style
  • Designing concurrent workflows or coordinating goroutines
  • Improving tests, test coverage, and test structure
  • Deciding package layout, visibility, or initialization patterns
  • Fixing error handling and return-value conventions

Best practices

  • Wrap errors with context using %w and check errors immediately; return errors instead of panicking
  • Accept interfaces and return concrete types; keep interfaces small and define them at the point of use
  • Pass context.Context as the first parameter and use errgroup for coordinating goroutines
  • Write table-driven tests, use t.Parallel() for independent cases, and prefer testing behavior over implementation details
  • Keep packages small and single-purpose, avoid package-level mutable state, and use internal/ for non-public code
  • Prefer short, consistent names (MixedCaps), avoid stuttering, and keep declarations close to use

Example use cases

  • Replace log.Fatal or panic with wrapped error returns and sentinel errors for expected conditions
  • Refactor a broad exported interface into a narrow consumer-defined interface and return a concrete struct
  • Coordinate multiple worker goroutines with errgroup.WithContext to avoid leaks and handle cancellation
  • Convert brittle unit tests into table-driven tests with t.Parallel() and helper functions using t.Helper()
  • Reorganize a repository to move non-public code into an internal/ package and remove global state

FAQ

When should I create a custom error type vs sentinel errors?

Use sentinel errors for simple, expected conditions you compare with errors.Is. Create custom error types when you need structured details, type assertions with errors.As, or extra context fields.

Is it OK to export interfaces?

Only export interfaces if callers need to implement them. Prefer defining small interfaces at the point of use so consumers control the contract.