Go Style Guide
Based on the Google Go Style Guide. Full reference docs are in references/.
Core Principles (in priority order)
- •Clarity — purpose and rationale are obvious to the reader
- •Simplicity — accomplish goals in the simplest way possible
- •Concision — high signal-to-noise ratio; no repetitive or extraneous code
- •Maintainability — easy to change correctly; APIs that grow gracefully
- •Consistency — matches the surrounding codebase; consistency breaks ties
When in doubt, optimize for the reader, not the author.
Formatting
- •Run
gofmt(orgoimports). All code must begofmt-formatted. No exceptions. - •No fixed line length. If a line feels too long, refactor rather than wrap.
- •Do not split lines before indentation changes (function declarations, conditionals).
- •Do not split long strings (e.g., URLs) across multiple lines.
Naming
General rules
- •Use
MixedCaps/mixedCaps(camel case). Nosnake_case, noALL_CAPS. - •Names should be short in small scopes, longer in large scopes.
- •Name based on role/meaning, not type or value.
Packages
- •Lowercase only, no underscores (e.g.,
tabwriter, nottab_writer). - •Avoid generic names:
util,helper,common,model,handler. - •Do not repeat the package name in function names:
yamlconfig.Parse, notyamlconfig.ParseYAMLConfig.
Functions and methods
- •Returning something → noun-like name:
Config.JobName() - •Doing something → verb-like name:
Config.WriteTo() - •No
Get/getprefix: useCounts()notGetCounts(). UseCompute/Fetchwhen the call is expensive or remote. - •Do not repeat the receiver type in the method name:
WriteTo, notWriteConfigTo. - •Do not repeat parameter names in the function name:
Override(dest, src)notOverrideFirstWithSecond.
Receivers
- •Short (1-2 letters), abbreviation of the type:
func (c *Config),func (ri *ResearchInfo). - •Never
thisorself. - •Consistent across all methods of a type.
Constants
- •
MixedCapslike everything else:MaxPacketSize, notMAX_PACKET_SIZEorkMaxBufferSize. - •Name based on role, not value.
Initialisms / Acronyms
- •Keep consistent case within the initialism:
URL,ID,DB,HTTP,GRPC. - •
urlPony(unexported) orURLPony(exported), neverUrlPony.
Variables
- •Scope-proportional length: single letter ok in tiny scopes, multi-word needed at file scope.
- •Avoid shadowing well-known identifiers (
err,ctx,ok).
Error Handling
- •Always handle errors. Never assign to
_unless intentional and documented. - •Return errors as the last return value.
- •Wrap errors with context:
fmt.Errorf("loading config: %w", err). - •Use
%w(not%v) when the caller may need toerrors.Is/errors.As. - •Error strings are lowercase and have no trailing punctuation (they get composed).
- •Sentinel errors use
errors.New:var ErrNotFound = errors.New("not found"). - •Custom error types implement the
errorinterface viaError() string.
go
// Good:
if err := doSomething(); err != nil {
return fmt.Errorf("doing something: %w", err)
}
// Good — signal a non-obvious condition:
if err := doSomething(); err == nil { // if NO error
// ...
}
Packages and Imports
- •Group imports: stdlib → external → internal. Use
goimportsto manage. - •Rename imports only when necessary to avoid collisions; use the same alias consistently across files.
- •Avoid import cycles.
- •Avoid "utility" packages that become catch-alls. Prefer focused, well-named packages.
Interfaces
- •Define interfaces at the point of use (consumer side), not in the package that implements them.
- •Keep interfaces small. Prefer one-method interfaces.
- •Do not add interfaces preemptively — only when you have multiple implementations or need to abstract for testing.
- •Document what the interface guarantees, not just what it has.
Testing
- •Use table-driven tests for parameterized cases.
- •Name test functions:
TestFoo,TestFoo_condition,BenchmarkFoo. - •Test package naming:
package foo(whitebox) orpackage foo_test(blackbox). - •Prefer
t.Errorfovert.Fatalfunless the test truly cannot continue. - •Provide clear failure messages:
t.Errorf("Foo(%v) = %v, want %v", input, got, want). - •Test helper packages: append
testto the production package name (e.g.,creditcardtest).
go
// Good: table-driven test
tests := []struct {
name string
input string
want int
}{
{"empty", "", 0},
{"single", "a", 1},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := Len(tt.input)
if got != tt.want {
t.Errorf("Len(%q) = %d, want %d", tt.input, got, tt.want)
}
})
}
Concurrency
- •Prefer channels for communicating between goroutines; prefer mutexes for protecting shared state.
- •Document goroutine lifetimes. Make it clear who is responsible for stopping a goroutine.
- •Use
context.Contextfor cancellation and deadline propagation; pass it as the first parameter. - •Name context variable
ctxconsistently.
Complexity & Abstractions
- •Use the simplest mechanism sufficient: core language construct → stdlib → well-known library.
- •Add complexity deliberately and document why.
- •Do not add abstractions (interfaces, helper functions) until they're needed by multiple callers.
- •A helper that hides critical logic makes future bugs more likely.
Common Pitfalls
- •Don't use
=where:=is needed (or vice versa) — the difference can be subtle. - •Closures capturing loop variables: capture by copy or use
t.Parallel()patterns. - •Avoid named return values except in short functions where they aid documentation.
- •Don't ignore the second return value from map lookups when the zero value is meaningful.
Reference Documents
| Document | Purpose |
|---|---|
| references/guide.md | Core style guide (normative, canonical) |
| references/decisions.md | Detailed style decisions with rationale |
| references/best-practices.md | Patterns for common situations |
| references/index.md | Overview and definitions |