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:
usernotuserService - •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:
ifor loop index,errfor errors - •Descriptive names for exports:
ReadConfignotRC
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{}orany): Use specific types when possible - •Global state: Prefer dependency injection
- •Naked returns: Always name what you're returning
- •Stuttering:
user.UserServiceshould beuser.Service - •init() functions: Prefer explicit initialization
- •Complex constructors: Use functional options pattern
When in Doubt
- •Refer to Effective Go
- •Check the Go standard library for examples
- •Use
go vetandstaticcheckfor automated guidance
This skill is powered by Gopher Guides training materials. For comprehensive Go training, visit gopherguides.com.