Uber Go style guide. Use when writing, reviewing, or modifying Go code to ensure it follows best practices for style, performance, error handling, concurrency, and naming conventions.
Install
npx skillscat add ferhatelmas/goodies/uber-go-style Install via the SkillsCat registry.
SKILL.md
When writing or modifying Go code, follow these rules based on the Uber Go Style Guide.
Guidelines
Interfaces
- Never use pointers to interfaces; pass interfaces as values
- Verify interface compliance at compile time:
var _ http.Handler = (*Handler)(nil)
Receivers
- Value receivers can be called on pointers and values; pointer receivers only on pointers or addressable values
Mutexes
- Zero-value
sync.Mutexandsync.RWMutexare valid; don't usenew(sync.Mutex) - Don't embed mutexes in structs; use a named field
mu sync.Mutex
Slices and Maps at Boundaries
- Copy slices and maps received as arguments if you store them (prevent caller mutation)
- Copy slices and maps before returning them if they expose internal state
Defer
- Use
deferto clean up resources (files, locks). The overhead is negligible
Channels
- Channel size should be one or unbuffered (zero). Any other size requires strong justification
Enums
- Start enums at one with
iota + 1unless zero value is a meaningful default
Time
- Use
time.Timefor instants,time.Durationfor periods - Include unit in field names when
time.Durationcan't be used:IntervalMillis - Use RFC 3339 for timestamp strings
Errors
- Use
errors.Newfor static errors,fmt.Errorffor dynamic - Export error vars (
ErrFoo) for caller matching viaerrors.Is; use custom types withErrorsuffix forerrors.As - Wrap errors with
%w(caller can match) or%v(opaque); avoid "failed to" prefixes: use"new store: %w"not"failed to create new store: %w" - Prefix exported error vars with
Err, unexported witherr - Handle errors once: either wrap and return, or log and degrade gracefully; never log AND return
Type Assertions
- Always use the "comma ok" form:
t, ok := i.(string)
Panics
- Don't panic in production code; return errors instead
- In tests, use
t.Fatalnotpanic - Only acceptable for program initialization:
template.Must(...)
Atomics
- Prefer
go.uber.org/atomictypes (atomic.Bool, etc.) over rawsync/atomicfor type safety
Mutable Globals
- Avoid mutable globals; use dependency injection instead
Embedding Types
- Don't embed types in public structs; delegate methods explicitly
- Embedding leaks implementation details and inhibits type evolution
Built-In Names
- Never shadow predeclared identifiers (
error,string,len,cap, etc.)
init()
- Avoid
init(). Make it deterministic, no I/O, no global state mutation - Prefer
var _defaultFoo = defaultFoo()or initialization inmain()
Exit
- Call
os.Exitorlog.Fatalonly inmain(); all other functions return errors - Prefer a single
run() errorfunction called frommain()for testability
Field Tags
- Always use field tags in marshaled structs:
json:"price"
Goroutines
- Don't fire-and-forget goroutines; every goroutine must have a way to stop and be waited on
- Use
sync.WaitGroupfor multiple goroutines, adonechannel for single ones - No goroutines in
init(); expose objects withShutdown()/Close()methods
Performance
- Use
strconvoverfmtfor primitive-to-string conversion - Don't repeatedly convert fixed strings to
[]byte; do it once - Specify capacity for slices:
make([]T, 0, size)and maps:make(map[K]V, size)
Style
Formatting
- Soft line length limit of 99 characters
- Be consistent above all else
Declarations
- Group similar declarations (
const,var,type) but only group related items - Two import groups: standard library, then everything else (separated by blank line)
Naming
- Package names: lowercase, no underscores, short, not plural, not "common/util/shared/lib"
- Function names: MixedCaps; test functions may use underscores for grouping
- Import aliases only when package name doesn't match last path element or on conflict
- Prefix unexported globals with
_(except error vars which useerrprefix)
Functions
- Sort functions by rough call order; group by receiver
- Exported functions first, after struct/const/var;
NewXYZ()right after type definition - Utility functions at end of file
Control Flow
- Reduce nesting: handle errors/special cases first, return early
- Eliminate unnecessary else: use default value + conditional override
- Reduce variable scope:
if err := doThing(); err != nil
Variables
- Use
:=for local variables with explicit values - Use
varfor zero-value declarations and empty slices nilis a valid slice; returnnilnot[]int{}; check emptiness withlen(s) == 0
Parameters
- Avoid naked bool parameters; use C-style comments or custom types for clarity
Strings
- Use raw string literals to avoid escaping:
`unknown error:"test"`
Structs
- Use field names in struct initialization (enforced by
go vet) - Omit zero-value fields unless they provide meaningful context
- Use
var s MyStructfor zero-value structs, nots := MyStruct{} - Use
&T{Name: "bar"}notnew(T)for struct references - Place embedded types at top of field list, separated by blank line
Maps
- Use
make(map[K]V)for empty/programmatic maps; use literals for fixed sets - Provide capacity hints when size is known
Printf
- Declare format strings as
constforgo vetanalysis - Name Printf-style functions with
fsuffix:Wrapf, notWrap
Patterns
Test Tables
- Use table-driven tests with subtests for repetitive test logic
- Name the slice
tests, each casett, usegive/wantprefixes - Avoid complex conditional logic in table tests; split into separate test functions instead
- For parallel table tests, ensure loop variables are scoped correctly
Functional Options
- Use the functional options pattern for constructors with 3+ parameters
- Implement with an
Optioninterface and unexportedoptionsstruct - Prefer concrete types over closures for debuggability and testability
Linting
- Run at minimum: errcheck, goimports, revive, govet, staticcheck
- Use golangci-lint as the lint runner
- Lint consistently across the entire codebase