Go Coding Guidelines
Idiomatic Go
- Write Go, not Java/C++. Keep interfaces small and implicit.
- Prefer composition over embedding; use embedding only when it genuinely fits.
- Use
defer for resource cleanup immediately after acquiring the resource.
- Goroutines and channels only when concurrency is truly needed.
- Design types so their zero value is already usable.
Project Structure
- Package names: short, lowercase, no underscores. Plural only for utility packages (
bytes, strings).
- Standard Go Project Layout:
cmd/ — entry points (one subdirectory per binary)
internal/ — non-exported code
pkg/ — exportable libraries (use sparingly)
- One file per logical unit; filename in
snake_case (e.g., order_repository.go).
- Test files:
*_test.go; integration tests: *_component_test.go.
Code Style
gofmt / goimports mandatory — never format manually.
- Short variable names in narrow scope (
i, err, r, w); descriptive names in wide scope.
- Name return values only when it meaningfully improves readability.
- Comments explain the why, not the what. No comments for obvious code.
- All exported symbols have a GoDoc comment starting with the symbol name.
Naming
- Functions with side effects: verbs (
send, save). Pure functions: nouns (payload, hex).
- Predicates:
Is / Has prefix.
- Type aliases for domain concepts:
type Amount float64, type OrderNumber string.
Error Handling
- Always handle errors — using
_ for errors is forbidden.
- Wrap errors with context:
fmt.Errorf("open config file: %w", err).
- Check sentinel errors with
errors.Is; typed errors with errors.As.
panic only for unrecoverable startup failures (e.g., MustNew... constructors).
- Error return is always last.
Interfaces and Dependencies
- Define interfaces in the consuming package, not the implementing one.
- Verify interface compliance at compile time:
var _ MyInterface = (*myImpl)(nil).
- Constructors return interfaces, not concrete types — unless the package is
internal.
Modern Go (Prefer Where Applicable)
any instead of interface{}.
- Generics (1.18+): use when type parameters genuinely simplify the code.
errors.Join (1.20+) for combining multiple errors.
- Range over integers (1.22+):
for i := range n { ... }.
slices / maps packages (1.21+) instead of manual loops.
Logging (zap)
- Pass logger as
*zap.SugaredLogger — never as a global variable.
- Use named loggers for subsystems:
log.Named("order-service").
- Pass fields as key-value pairs; never interpolate into the message string.
Testing
- Table-driven tests are the default for multiple input cases.
- Call
t.Parallel() in independent tests.
- No
time.Sleep — use channels or sync.WaitGroup.
- Mock only at package boundaries; test internal logic directly.
- Use
testify/require for preconditions, testify/assert for assertions.
- Structure test bodies with
// GIVEN, // WHEN, // THEN.
- BDD integration tests with Godog; include a sample
.feature file and step definitions.