Skip to content

Latest commit

 

History

History

README.md

Go Code Style

Binding style rules for Go projects. Prioritize correctness, explicitness, simplicity — never cleverness or abstraction.

This guide extends and defers to Google's Go Style Guide (Style Guide, Style Decisions, and Best Practices). Where our guidance conflicts with Google's, Google wins — Google's rules represent the canonical standard for idiomatic Go. This guide adds project-specific conventions on top: assertion density (TigerBeetle-inspired), 70-line function limit, explicit limits on all loops/queues/retries, and EG platform testing patterns. Additional supplemental guidance is drawn from Effective Go and the Uber Go Style Guide.

Style Priorities (from Google)

  1. Clarity — code's purpose is clear to the reader.
  2. Simplicity — the simplest approach that accomplishes the goal.
  3. Concision — high signal-to-noise ratio.
  4. Maintainability — easy to modify correctly over time.
  5. Consistency — matches the surrounding codebase.

Resolve rule conflicts in this order. Consistency is the tiebreaker, never an override.


Table of Contents

# Document Scope
01 Formatting & Tooling gofmt, golangci-lint, imports, line length, conditionals, Yoda conditions, function size
02 Naming Conventions Scope-proportional names, initialisms, receiver names, getters/setters, repetition rules, test doubles
03 Error Handling %w placement, in-band errors, error strings, sentinels, typed errors, Must functions, panic discipline
04 Concurrency context rules, custom-context ban, synchronous preferred, goroutine lifetimes, channels, mutexes
05 API Design Interfaces (small, consumer-defined), options (struct vs variadic), generics, type aliases, receiver types
06 Testing Table-driven tests, assertion philosophy, t.Fatal from goroutines, useful failures, cmp.Diff, acceptance tests
07 Package Organization Directory layout, internal packages, import grouping (stdlib/external/pb/side-effect), dot/blank imports
08 Logging log/slog, levels, expensive-log guarding, no flags in libraries, PII, sensitive data masking
09 Serialization encoding/json, time handling, %q, struct/map init, validation, composite literals
10 Resource Management defer, context, timeouts, connection pooling, crypto/rand for secrets, graceful shutdown
11 Documentation GoDoc, package comments, main binary comments, named result parameters, signal boosting
12 Variables & Declarations := vs var, struct field names, composite literals, channel direction, nil slices, raw strings
13 Performance Hints strconv over fmt, string-to-byte reuse, stack vs heap, size hints, string building

Cross-Cutting Concerns

Security, performance, and git practices are covered in the root-level code style guide.

Design Goals

Correctness > performance > developer experience. When they conflict, this ordering decides.

Philosophy

Go is closest to the Zig/Elixir ideal: no inheritance, no classes, no exceptions -- just structs, functions, interfaces, and goroutines. Assertion discipline, bounded execution, and explicit options are drawn from TigerBeetle's Tiger Style -- Go is the natural home for these principles.

  1. Structs + functions, no objects. Structs hold data. Functions operate on data. Interfaces define behavior contracts. No inheritance, no method overriding.
  2. Errors are values -- handle every one. No discarding errors with _. Every error is wrapped with context and propagated.
  3. Explicit over implicit. No init() side effects. No global state. No magic. Dependencies are parameters.
  4. Immutable by default. Return new values over mutating. Use exported fields when the zero value is meaningful and direct access is fine — this is the Go default (http.Request.URL, http.Request.Method). Reach for an unexported field + accessor only when you need to enforce an invariant, hide a computation, or keep the public API stable across internal changes. Accessors don't take a Get prefix — see naming conventions §Getters and Setters.
  5. Accept interfaces, return structs. Narrowest interface in, concrete type out. Interfaces are discovered, not designed upfront.
  6. Transform, don't mutate. Process data through function pipelines. Each function takes input, returns new output.
  7. Always say why. Comments explain reasoning, not mechanics.
  8. Assert aggressively. Validate at every public boundary. Return errors, never accept garbage silently.
  9. Think about performance from the outset. Stack over heap. Buffer reuse. Understand escape analysis.
  10. Zero technical debt. Do it right the first time.

Deviations from Upstream

This guide takes Google's Go Style Guide as canonical. The entries below are places where idiomatic Go — codified by Google and Effective Go — wins over the root canon's cross-language defaults, plus the function-size cap Google does not address. Each is recorded so it can be revisited surgically.

Rule Upstream position Our position Why
Pointer-receiver mutation and Set* setters Effective Go / Google: pointer receivers mutate in place; setters take a Set prefix Permitted and idiomatic, despite the root canon's "immutable by default / transform, don't mutate" Go has no with-expression and no cheap structural copy; a pointer receiver that mutates is the language's native idiom for stateful types, and Set* is the canonical setter name. Effective Go wins for Go. The canon's spirit still holds — return new values where it's natural, and copy slices and maps across boundaries (see 05-api-design.md).
Bounded recursion in library code Not addressed Recursion permitted only where its depth is provably bounded by an explicit, asserted limit The canon leans no-recursion, but a depth-capped parser that checks an incrementing counter against a hard cap and recovers at the public boundary is provably bounded — the property the canon actually wants. Unbounded recursion stays banned. See 05-api-design.md and 03-error-handling.md.
Function size No upstream cap (Google rejects funlen) 70-line hard cap; aim 20–40 Owner decision; Tiger Style discipline. The 70 here is the level the TS, Kotlin, and C# caps are set against. See 01-formatting-and-tooling.md.

Zero Technical Debt

What exists meets the design goals. Do it right the first time; the second chance may never come.

Perfection over technical debt — debt never gets paid