Go Standard
Go Standard
This standard governs human-authored Go in Beef. Generated Go follows the generator contract first and is exempt from rules that conflict with generated output.
Precedence
Apply Go guidance in this order:
- Idiomatic Go and Rob Pike’s Go proverbs.
- The Go standard library style and official Go documentation.
- Effective Go and the Go Code Review Comments.
- The Uber Go Style Guide where it sharpens a choice not already settled above.
- Local Beef conventions in this document and scoped AGENTS.md files.
When these sources disagree, pick the simplest idiomatic Go shape that fits the surrounding package and can be enforced by lint, tests, or review.
Proverbs In Practice
Use the Go proverbs as operating rules, not slogans:
- Clear is better than clever.
- A little copying is better than a little dependency.
- Concurrency is not parallelism.
- Do not communicate by sharing memory; share memory by communicating.
- Channels orchestrate; mutexes serialize.
- The bigger the interface, the weaker the abstraction.
- Make the zero value useful where practical.
- Interface satisfaction should be implicit.
- Errors are values.
- Documentation is for users.
- Gofmt’s style is no one’s favorite, yet gofmt is everyone’s favorite.
These principles are especially important in shared service paths such as billing, media, storage, auth, workers, and generated-client boundaries.
Package Shape
Prefer boring package boundaries:
- Keep package names lowercase, short, and stutter-free.
- Put private application code under
internal/. - Keep
mainpackages thin: parse config, wire dependencies, start the service, handle shutdown. - Accept small interfaces at the use site; return concrete types from constructors.
- Do not introduce an interface just to mock one implementation. Add it when it describes a real boundary.
- Keep generated code in generated-code locations and do not hand-edit it.
Filenames
Human-authored Go source filenames must match:
^[a-z0-9]+(_test)?\.go$Examples:
client.gobillingaddress.goprocessorconcurrency_test.go
Generated/codegen files are exempt when produced by the generator or marked generated in their header. Current exempt shapes include:
*_templ.gomock_*.goand*_mock.go*.pb.go,*_grpc.pb.go, and Connect output*_gen.go,wire_gen.go,*_generated.go, and Swagger/OpenAPI outputapps/sirloin/pkg/brain-client/
If a generated file can be configured to follow the rule without fighting the generator, prefer configuring the generator. Otherwise, leave generated output alone.
Constructors And Dependencies
Use explicit constructor injection:
- Constructors use
New*when they create a reusable concrete type. - Dependencies are parameters, not package globals.
- Validate required dependencies in constructors when nil would panic later.
- Prefer functional options only when defaults and optional configuration are genuinely useful.
- Avoid hidden background work in constructors unless the type clearly owns that lifecycle and has a
Closeor stop path.
Context
Use context.Context consistently:
ctx context.Contextis the first parameter after the receiver.- Test helpers may keep
t *testing.Tfirst. - Do not store contexts in structs.
- Propagate caller contexts through I/O, database, RPC, HTTP, and worker paths.
- Use
context.Background()only at process roots, scheduled-job roots, tests, and intentional fire-and-forget boundaries with bounded timeouts.
Errors
Errors are part of the API:
- Wrap errors with context using
fmt.Errorf("...: %w", err). - Use sentinel errors only for stable conditions callers branch on.
- Use typed errors when callers need structured fields.
- Prefer early returns over nested error handling.
- Do not log and return the same error unless the log adds operational context at a boundary.
Concurrency
Prefer simple ownership:
- Use goroutines only when there is a clear lifecycle and cancellation story.
- Prefer
errgroup,sync.WaitGroup, channels, or mutexes according to ownership. Do not use channels as decoration. - Bound background loops with context, stop channels, or process shutdown hooks.
- Protect shared maps and mutable state with explicit synchronization.
- Keep retry, backoff, and circuit-breaker behavior close to the integration boundary.
Testing
Use Go’s standard test shape:
- Tests live next to source as
*_test.go. - Prefer table-driven tests when cases share one behavior contract.
- Call
t.Helper()in helpers that fail tests. - Use
t.Cleanupfor teardown. - Keep mocks generated or local to the package boundary they exercise.
- Test behavior and invariants, not implementation trivia.
- For billing, auth, storage, generated contracts, or worker changes, broaden verification beyond the touched package.
Formatting And Lint
Formatting is non-negotiable:
- Run
gofmtorgo fmton touched Go files. - Run the owning service lint gate before finishing:
make lintinapps/sirloin,apps/strip, orapps/round. - For standard Go service changes, run
make run-testsin the owning service when practical. - If a local native dependency blocks verification, report the exact missing dependency and the command that failed.
Review Checklist
Before considering Go work done, check:
- Is the package shape boring and idiomatic?
- Are interfaces small and defined where they are consumed?
- Are contexts propagated and ordered correctly?
- Are errors wrapped at useful boundaries?
- Are goroutines cancellable or intentionally process-scoped?
- Are generated files untouched unless their source changed?
- Do filenames follow the source filename rule or qualify for a generated-file exemption?
- Did lint and the smallest meaningful tests pass?