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.
- Clarity — code's purpose is clear to the reader.
- Simplicity — the simplest approach that accomplishes the goal.
- Concision — high signal-to-noise ratio.
- Maintainability — easy to modify correctly over time.
- Consistency — matches the surrounding codebase.
Resolve rule conflicts in this order. Consistency is the tiebreaker, never an override.
| # | 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 |
Security, performance, and git practices are covered in the root-level code style guide.
Correctness > performance > developer experience. When they conflict, this ordering decides.
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.
- Structs + functions, no objects. Structs hold data. Functions operate on data. Interfaces define behavior contracts. No inheritance, no method overriding.
- Errors are values -- handle every one. No discarding errors with
_. Every error is wrapped with context and propagated. - Explicit over implicit. No
init()side effects. No global state. No magic. Dependencies are parameters. - 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 aGetprefix — see naming conventions §Getters and Setters. - Accept interfaces, return structs. Narrowest interface in, concrete type out. Interfaces are discovered, not designed upfront.
- Transform, don't mutate. Process data through function pipelines. Each function takes input, returns new output.
- Always say why. Comments explain reasoning, not mechanics.
- Assert aggressively. Validate at every public boundary. Return errors, never accept garbage silently.
- Think about performance from the outset. Stack over heap. Buffer reuse. Understand escape analysis.
- Zero technical debt. Do it right the first time.
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. |
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