Skip to content

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.