Go Development Patterns
Idiomatic Go patterns and best practices for building robust, efficient, and maintainable applications.
When to Activate
- •Writing new Go code
- •Reviewing Go code
- •Refactoring existing Go code
- •Designing Go packages/modules
Core Principles
1. Simplicity and Clarity
go
// Good: Clear and direct
func GetUser(id string) (*User, error) {
user, err := db.FindUser(id)
if err != nil {
return nil, fmt.Errorf("get user %s: %w", id, err)
}
return user, nil
}
2. Make the Zero Value Useful
go
// Good: Zero value is useful
type Counter struct {
mu sync.Mutex
count int // zero value is 0, ready to use
}
3. Accept Interfaces, Return Structs
go
func ProcessData(r io.Reader) (*Result, error) {
data, err := io.ReadAll(r)
if err != nil {
return nil, err
}
return &Result{Data: data}, nil
}
Error Handling Patterns
Error Wrapping with Context
go
func LoadConfig(path string) (*Config, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("load config %s: %w", path, err)
}
var cfg Config
if err := json.Unmarshal(data, &cfg); err != nil {
return nil, fmt.Errorf("parse config %s: %w", path, err)
}
return &cfg, nil
}
Custom Error Types
go
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("validation failed on %s: %s", e.Field, e.Message)
}
var (
ErrNotFound = errors.New("resource not found")
ErrUnauthorized = errors.New("unauthorized")
)
Error Checking with errors.Is and errors.As
go
if errors.Is(err, sql.ErrNoRows) {
log.Println("No records found")
return
}
var validationErr *ValidationError
if errors.As(err, &validationErr) {
log.Printf("Validation error: %s", validationErr.Field)
}
Concurrency Patterns
Worker Pool
go
func WorkerPool(jobs <-chan Job, results chan<- Result, numWorkers int) {
var wg sync.WaitGroup
for i := 0; i < numWorkers; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for job := range jobs {
results <- process(job)
}
}()
}
wg.Wait()
close(results)
}
Context for Cancellation
go
func FetchWithTimeout(ctx context.Context, url string) ([]byte, error) {
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, fmt.Errorf("create request: %w", err)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, fmt.Errorf("fetch %s: %w", url, err)
}
defer resp.Body.Close()
return io.ReadAll(resp.Body)
}
Avoiding Goroutine Leaks
go
// Good: Properly handles cancellation
func safeFetch(ctx context.Context, url string) <-chan []byte {
ch := make(chan []byte, 1) // Buffered channel
go func() {
data, err := fetch(url)
if err != nil {
return
}
select {
case ch <- data:
case <-ctx.Done():
}
}()
return ch
}
Interface Design
Small, Focused Interfaces
go
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
Define Interfaces Where They're Used
go
// In the consumer package, not the provider
package service
type UserStore interface {
GetUser(id string) (*User, error)
SaveUser(user *User) error
}
Struct Design: Functional Options
go
type Option func(*Server)
func WithTimeout(d time.Duration) Option {
return func(s *Server) { s.timeout = d }
}
func NewServer(addr string, opts ...Option) *Server {
s := &Server{addr: addr, timeout: 30 * time.Second}
for _, opt := range opts {
opt(s)
}
return s
}
Memory and Performance
go
// Preallocate slices
results := make([]Result, 0, len(items))
// Use strings.Builder for concatenation
var sb strings.Builder
sb.WriteString("hello")
// Use sync.Pool for frequent allocations
var bufferPool = sync.Pool{
New: func() interface{} { return new(bytes.Buffer) },
}
Go Idioms Quick Reference
| Idiom | Description |
|---|---|
| Accept interfaces, return structs | Flexible input, concrete output |
| Errors are values | First-class, not exceptions |
| Make the zero value useful | No required init |
| Return early | Handle errors first |
| A little copying > a little dependency | Avoid unnecessary deps |
| Clear is better than clever | Readability first |