home / skills / gopherguides / gopher-ai / 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-practicesReview the files below or copy the command above to add this skill to your agents.
---
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).*
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.
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 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.