Dave Cheney Style Guide
Overview
Dave Cheney is a Go contributor, prolific blogger, and author of "Practical Go." His talks and writing focus on real-world Go patterns, error handling philosophy, and performance optimization without sacrificing readability.
Core Philosophy
"Clear is better than clever."
"Errors are just values."
"Make the zero value useful."
Cheney believes in practical, production-ready code. His approach: write clear code first, understand the performance characteristics, optimize only what matters.
Design Principles
Errors Are Values: Handle them, wrap them, or return them—but never ignore them.
Clear Over Clever: If it needs a comment, rewrite it.
Performance Awareness: Know the cost of operations, but don't prematurely optimize.
Package Design: Small, focused packages with clear responsibilities.
When Writing Code
Always
-
Handle every error explicitly
-
Wrap errors with context using fmt.Errorf and %w
-
Use structured logging
-
Write benchmarks for hot paths
-
Keep packages focused and small
-
Document exported symbols
Never
-
Use _ to ignore errors (except in specific cases)
-
panic for expected error conditions
-
Create "utils" or "common" packages
-
Use package-level variables for state
-
Log and return an error (do one or the other)
Prefer
-
errors.Is and errors.As over type assertions
-
Wrapping errors over creating new ones
-
Returning early over deep nesting
-
Small interfaces (1-3 methods)
-
Dependency injection over globals
Code Patterns
Error Handling Philosophy
// BAD: Ignoring errors data, _ := ioutil.ReadFile(filename)
// BAD: Log and return (double handling) if err != nil { log.Printf("failed to read: %v", err) return err }
// GOOD: Add context and return if err != nil { return fmt.Errorf("read config %s: %w", filename, err) }
// GOOD: Handle completely (don't return) if err != nil { log.Printf("failed to read %s, using defaults: %v", filename, err) return defaultConfig() }
Error Wrapping Strategy
// Build error chains with context func LoadUser(id string) (*User, error) { data, err := db.Query(id) if err != nil { return nil, fmt.Errorf("load user %s: %w", id, err) }
user, err := parseUser(data)
if err != nil {
return nil, fmt.Errorf("load user %s: %w", id, err)
}
return user, nil
}
// Callers can check for specific errors user, err := LoadUser(id) if errors.Is(err, sql.ErrNoRows) { return nil, ErrUserNotFound }
// Or extract error types var queryErr *QueryError if errors.As(err, &queryErr) { log.Printf("query failed: %s", queryErr.Query) }
Sentinel Errors Done Right
// Define sentinel errors at package level var ( ErrNotFound = errors.New("not found") ErrPermission = errors.New("permission denied") ErrTimeout = errors.New("operation timed out") )
// Wrap them with context func GetItem(id string) (*Item, error) { item, ok := store[id] if !ok { return nil, fmt.Errorf("item %s: %w", id, ErrNotFound) } return item, nil }
// Callers check with errors.Is (handles wrapping) if errors.Is(err, ErrNotFound) { // handle not found }
Package Organization
// BAD: Kitchen sink packages package utils package common package helpers
// GOOD: Focused packages by responsibility package user // User domain logic package storage // Storage abstraction package http // HTTP handlers
// Package should have ONE primary type or purpose // user/user.go package user
type User struct { ... } type Service struct { ... } func New(...) *Service { ... }
Dependency Injection
// BAD: Hard-coded dependencies type Server struct{}
func (s *Server) HandleUser(w http.ResponseWriter, r *http.Request) { user, err := db.GetUser(r.Context(), userID) // Global db! // ... }
// GOOD: Injected dependencies type Server struct { users UserStore logger Logger }
type UserStore interface { Get(ctx context.Context, id string) (*User, error) }
func NewServer(users UserStore, logger Logger) *Server { return &Server{users: users, logger: logger} }
func (s *Server) HandleUser(w http.ResponseWriter, r *http.Request) { user, err := s.users.Get(r.Context(), userID) // ... }
// Testing is now trivial func TestHandleUser(t *testing.T) { mock := &MockUserStore{} srv := NewServer(mock, testLogger) // ... }
Performance-Aware Code
// Know the cost: string concatenation // BAD: Creates many allocations func join(items []string) string { result := "" for _, item := range items { result += item + "," // Allocates each time! } return result }
// GOOD: Pre-allocate with strings.Builder func join(items []string) string { var b strings.Builder for i, item := range items { if i > 0 { b.WriteString(",") } b.WriteString(item) } return b.String() }
// Know the cost: slice operations // BAD: May cause unexpected allocations func process(items []Item) { filtered := items[:0] // Reuses backing array - be careful! for _, item := range items { if item.Valid { filtered = append(filtered, item) } } }
// GOOD: Clear intent func process(items []Item) []Item { filtered := make([]Item, 0, len(items)) for _, item := range items { if item.Valid { filtered = append(filtered, item) } } return filtered }
Functional Options with Validation
type serverOptions struct { addr string readTimeout time.Duration writeTimeout time.Duration }
type ServerOption func(*serverOptions) error
func WithAddr(addr string) ServerOption { return func(o *serverOptions) error { if addr == "" { return errors.New("addr cannot be empty") } o.addr = addr return nil } }
func WithTimeouts(read, write time.Duration) ServerOption { return func(o *serverOptions) error { if read <= 0 || write <= 0 { return errors.New("timeouts must be positive") } o.readTimeout = read o.writeTimeout = write return nil } }
func NewServer(opts ...ServerOption) (*Server, error) { options := serverOptions{ addr: ":8080", readTimeout: 30 * time.Second, writeTimeout: 30 * time.Second, }
for _, opt := range opts {
if err := opt(&options); err != nil {
return nil, fmt.Errorf("invalid option: %w", err)
}
}
return &Server{options: options}, nil
}
Mental Model
Cheney writes code by asking:
-
How does this fail? Handle that case explicitly.
-
What's the cost? Know allocations, copies, syscalls.
-
Is this clear? Would a new team member understand it?
-
Is this testable? Can I inject dependencies?
Cheney's Laws
-
Don't log and return an error—do one or the other
-
Wrap errors with context, don't just return them
-
If a function can fail, it should return an error
-
Small interfaces are better than large ones
-
Accept interfaces, return structs