Go Style Guide Skill
This skill defines practical Go coding style and engineering patterns optimized for:
- humans reading/maintaining code
- coding agents generating/restructuring code reliably
- production readiness (correctness, testability, performance)
Use this skill anytime you are working with Go: new code, refactors, reviews, and architecture decisions.
TL;DR
- Design for testability first; inject dependencies and keep logic pure.
- Prefer
Config in → concrete struct out; validate and default in constructors.
- Errors are contracts: sentinel errors +
%w or errors.Join.
- Keep packages reusable: no hidden globals, no default logging.
- Benchmark hot paths before claiming wins.
- Return concrete types; accept interfaces at boundaries.
- Keep
main.go thin; app wiring lives in app/.
House Style Disclaimer
This is intentionally opinionated. It favors consistency and long-term maintainability over accommodating every Go style preference.
Quick Rules Table
| Topic |
Rule |
Reference |
| Testability |
Use interfaces at boundaries so users can create fakes/mocks |
references/INTERFACES.md |
| Constructors |
Config in → concrete struct out; validate + default in New |
references/CONFIG.md |
| Errors |
Prefer sentinel errors; wrap with %w or errors.Join |
references/ERRORS.md |
| Logging |
Packages do not log by default; app owns logging |
references/LOGGING.md |
| Interfaces |
Accept interfaces at boundaries; return concretes |
references/INTERFACES.md |
| Layout |
Keep packages shallow; avoid utils//common |
references/LAYOUT.md |
| Entry Points |
main.go is wiring only |
references/LAYOUT.md |
| Benchmarks |
Benchmark hot paths; use b.ReportAllocs() |
references/BENCHMARKS.md |
| Reviews |
Use the checklist when reviewing Go changes |
references/REVIEW-CHECKLIST.md |
Common Pitfalls
- Returning interfaces by default instead of concrete types.
- Logging in reusable packages instead of returning errors.
- Passing global app config through packages rather than local
Config.
- Hiding dependencies behind package-level globals.
- Over-structuring directory layouts in small projects.
- Shipping changes that claim performance wins without benchmarks.
Core Principles
- Testability is first-class
- Prefer designs that are easy to test without booting an entire application.
- Inject dependencies explicitly.
- Keep pure logic isolated.
- Config-driven construction
- Prefer
Config in → struct out constructors.
- Validate at construction.
- Default explicitly.
- Errors are a contract
- Prefer sentinel errors where practical, especially in packages.
- Use
%w (or errors.Join) so callers can use errors.Is/As.
- Benchmark what matters
- Add benchmarks for performance-sensitive code paths.
- Avoid “it’s faster” claims without
go test -bench.
- Packages are reusable by default
- Keep packages domain-focused and individually testable.
- Avoid global state and hidden side effects.
Package Types
App package (orchestrator)
Owns:
- dependency wiring (DB, clients, loggers)
- lifecycle (start/stop)
- error policy (retry, ignore, crash)
- logging and metrics policy
Non-app packages (reusable units)
Rules:
- No direct logging (see
references/LOGGING.md)
- Return errors, don’t hide them
- Define a local
Config/Opts contract
- Accept initialized dependencies (DB/client/etc), do not create them internally
Directory Structure
Services / apps
cmd/<appname>/main.go for entrypoints
- Keep
main.go thin: parse config, wire dependencies, call app.Run(ctx, cfg)
Example:
cmd/myapp/main.go
pkg/...
pkg/app/...
Libraries
- Packages at top-level directories, or under
pkg/ if it improves clarity for newcomers.
- Avoid junk drawers (
utils, common) unless they truly represent a domain.
Constructors and Config
Preferred constructor shape
New(cfg Config) (*T, error) or Dial(cfg Config) (*T, error)
- Validate + default inside constructor
- Return a concrete type by default
Interfaces: yes, when they pay rent
Interfaces improve testability, but don’t return interfaces by default.
Prefer:
- accept interfaces at boundaries (dependency injection)
- return concrete types, unless there is a clear multi-impl boundary or you must hide implementation
Canonical Config Example (Generic Executor)
This is the “accepted” pattern: Config drives behavior, constructor returns a concrete struct, and test seams are explicit.
// Runner provides a minimal contract for executing work.
type Runner interface {
Run(ctx context.Context, input []byte) ([]byte, error)
}
// Config configures behavior and dependencies.
type Config struct {
Timeout time.Duration
Runner Runner
}
// executor implements Runner-backed execution.
type executor struct {
cfg Config
run Runner
}
Guidance:
- Config is owned by the package (not passed around as global app config)
- Defaults apply in
New (e.g., timeout, Runner)
- Validate at construction when possible
Logging
Logging is owned by the application.
If a package must log (rare async/network/runtime cases), inject *slog.Logger via Config, default to discard, and keep structured logs.
See: references/LOGGING.md
Errors
Prefer sentinel errors in packages
- Export
var ErrX = errors.New("...") for stable meaning
- Wrap with
%w or use errors.Join so errors.Is works
Rules:
- Don’t use
%s to wrap errors (it breaks unwrap semantics)
- Add context with wrapping:
fmt.Errorf("doing X: %w", err)
File Organization and Efficiency
Go file layout (practical default)
Within a package:
- package comment (if primary file)
- imports
- constants / vars
- types (interfaces + structs)
- constructors
- methods
- functions (helpers last)
Avoid catch-all buckets like types.go, constants.go, util.go unless strongly justified.
Struct field efficiency
- Keep hot-path structs compact where it matters
- Consider field ordering to reduce padding (especially large arrays, bools, pointers)
- But do not micro-optimize without benchmarks
Benchmarks
Add benchmarks for:
- serialization/deserialization
- hot-path functions
- concurrency primitives and contention paths
- adapters/wrappers in tight loops
Benchmark rules:
- Use
b.ReportAllocs()
- Include realistic inputs
- Compare alternatives if proposing a change
Top-level Interfaces + Implementations
A preferred pattern:
- define a small boundary interface in a top-level package
- provide implementations in subpackages (
drivers/, backends/, etc.)
- keep contracts small and stable
Use when:
- multiple implementations exist (real + mock, or multiple backends)
- it clarifies architecture and enables testing
Reference Index
Use these supporting documents when deeper detail is needed:
references/LOGGING.md
Logging rules: default “no logging in packages,” exceptions, slog injection.
references/ERRORS.md
Sentinel-first error design, wrapping rules, errors.Is/As contracts.
references/CONFIG.md
Canonical Config struct patterns, constructor validation + defaults.
references/INTERFACES.md
Interface boundaries, driver patterns, testability-first design.
references/LAYOUT.md
File organization, struct field efficiency, package naming guidance.
references/BENCHMARKS.md
Benchmark expectations, templates, performance validation rules.
references/REVIEW-CHECKLIST.md
PR review rubric for humans and coding agents.
Converted and distributed by TomeVault — claim your Tome and manage your conversions.
1---2name: go-style-guide3description: Go engineering style guide for designing packages, services, and CLIs. Use for any Go work creating or reviewing packages/APIs, PR reviews, refactors/restructures, error/logging patterns, config/constructors, testing, and benchmarks. Use when this capability is needed.4---56# Go Style Guide Skill78This skill defines practical Go coding style and engineering patterns optimized for:9- **humans** reading/maintaining code10- **coding agents** generating/restructuring code reliably11- **production readiness** (correctness, testability, performance)1213Use this skill anytime you are working with Go: new code, refactors, reviews, and architecture decisions.1415---1617## TL;DR1819- Design for testability first; inject dependencies and keep logic pure.20- Prefer `Config` in → concrete struct out; validate and default in constructors.21- Errors are contracts: sentinel errors + `%w` or `errors.Join`.22- Keep packages reusable: no hidden globals, no default logging.23- Benchmark hot paths before claiming wins.24- Return concrete types; accept interfaces at boundaries.25- Keep `main.go` thin; app wiring lives in `app/`.2627---2829## House Style Disclaimer3031This is intentionally opinionated. It favors consistency and long-term maintainability over accommodating every Go style preference.3233---3435## Quick Rules Table3637| Topic | Rule | Reference |38| --- | --- | --- |39| Testability | Use interfaces at boundaries so users can create fakes/mocks | `references/INTERFACES.md` |40| Constructors | `Config` in → concrete struct out; validate + default in `New` | `references/CONFIG.md` |41| Errors | Prefer sentinel errors; wrap with `%w` or `errors.Join` | `references/ERRORS.md` |42| Logging | Packages do not log by default; app owns logging | `references/LOGGING.md` |43| Interfaces | Accept interfaces at boundaries; return concretes | `references/INTERFACES.md` |44| Layout | Keep packages shallow; avoid `utils/`/`common` | `references/LAYOUT.md` |45| Entry Points | `main.go` is wiring only | `references/LAYOUT.md` |46| Benchmarks | Benchmark hot paths; use `b.ReportAllocs()` | `references/BENCHMARKS.md` |47| Reviews | Use the checklist when reviewing Go changes | `references/REVIEW-CHECKLIST.md` |4849---5051## Common Pitfalls5253- Returning interfaces by default instead of concrete types.54- Logging in reusable packages instead of returning errors.55- Passing global app config through packages rather than local `Config`.56- Hiding dependencies behind package-level globals.57- Over-structuring directory layouts in small projects.58- Shipping changes that claim performance wins without benchmarks.5960---6162## Core Principles63641) **Testability is first-class**65- Prefer designs that are easy to test without booting an entire application.66- Inject dependencies explicitly.67- Keep pure logic isolated.68692) **Config-driven construction**70- Prefer `Config in → struct out` constructors.71- Validate at construction.72- Default explicitly.73743) **Errors are a contract**75- Prefer **sentinel errors** where practical, especially in packages.76- Use `%w` (or `errors.Join`) so callers can use `errors.Is/As`.77784) **Benchmark what matters**79- Add benchmarks for performance-sensitive code paths.80- Avoid “it’s faster” claims without `go test -bench`.81825) **Packages are reusable by default**83- Keep packages domain-focused and individually testable.84- Avoid global state and hidden side effects.8586---8788## Package Types8990### App package (orchestrator)91Owns:92- dependency wiring (DB, clients, loggers)93- lifecycle (start/stop)94- error policy (retry, ignore, crash)95- logging and metrics policy9697### Non-app packages (reusable units)98Rules:99- No direct logging (see `references/LOGGING.md`)100- Return errors, don’t hide them101- Define a local `Config`/`Opts` contract102- Accept initialized dependencies (DB/client/etc), do not create them internally103104---105106## Directory Structure107108### Services / apps109- `cmd/<appname>/main.go` for entrypoints110- Keep `main.go` **thin**: parse config, wire dependencies, call `app.Run(ctx, cfg)`111112Example:113```114cmd/myapp/main.go115pkg/...116pkg/app/...117```118### Libraries119- Packages at top-level directories, or under `pkg/` if it improves clarity for newcomers.120- Avoid junk drawers (`utils`, `common`) unless they truly represent a domain.121122---123124## Constructors and Config125126### Preferred constructor shape127- `New(cfg Config) (*T, error)` or `Dial(cfg Config) (*T, error)`128- Validate + default inside constructor129- Return a **concrete** type by default130131### Interfaces: yes, when they pay rent132Interfaces improve testability, but don’t return interfaces *by default*.133134Prefer:135- **accept interfaces** at boundaries (dependency injection)136- **return concrete types**, unless there is a clear multi-impl boundary or you must hide implementation137138---139140## Canonical Config Example (Generic Executor)141142This is the “accepted” pattern: `Config` drives behavior, constructor returns a concrete struct, and test seams are explicit.143```go144// Runner provides a minimal contract for executing work.145type Runner interface {146 Run(ctx context.Context, input []byte) ([]byte, error)147}148149// Config configures behavior and dependencies.150type Config struct {151 Timeout time.Duration152 Runner Runner153}154155// executor implements Runner-backed execution.156type executor struct {157 cfg Config158 run Runner159}160```161Guidance:162163- Config is **owned by the package** (not passed around as global app config)164- Defaults apply in `New` (e.g., timeout, Runner)165- Validate at construction when possible166167---168169## Logging170171Logging is owned by the application.172173If a package must log (rare async/network/runtime cases), inject **`*slog.Logger`** via `Config`, default to discard, and keep structured logs.174175See: `references/LOGGING.md`176177---178179## Errors180181### Prefer sentinel errors in packages182183- Export `var ErrX = errors.New("...")` for stable meaning184- Wrap with `%w` or use `errors.Join` so `errors.Is` works185186Rules:187188- Don’t use `%s` to wrap errors (it breaks unwrap semantics)189- Add context with wrapping: `fmt.Errorf("doing X: %w", err)`190191---192193## File Organization and Efficiency194195### Go file layout (practical default)196197Within a package:1981991. package comment (if primary file)2002. imports2013. constants / vars2024. types (interfaces + structs)2035. constructors2046. methods2057. functions (helpers last)206207Avoid catch-all buckets like `types.go`, `constants.go`, `util.go` unless strongly justified.208209### Struct field efficiency210211- Keep hot-path structs compact where it matters212- Consider field ordering to reduce padding (especially large arrays, bools, pointers)213- But do not micro-optimize without benchmarks214215---216217## Benchmarks218219Add benchmarks for:220221- serialization/deserialization222- hot-path functions223- concurrency primitives and contention paths224- adapters/wrappers in tight loops225226Benchmark rules:227228- Use `b.ReportAllocs()`229- Include realistic inputs230- Compare alternatives if proposing a change231232---233234## Top-level Interfaces + Implementations235236A preferred pattern:237238- define a small boundary interface in a top-level package239- provide implementations in subpackages (`drivers/`, `backends/`, etc.)240- keep contracts small and stable241242Use when:243244- multiple implementations exist (real + mock, or multiple backends)245- it clarifies architecture and enables testing246247---248249## Reference Index250251Use these supporting documents when deeper detail is needed:252253- [references/LOGGING.md](references/LOGGING.md)254 Logging rules: default “no logging in packages,” exceptions, slog injection.255256- [references/ERRORS.md](references/ERRORS.md)257 Sentinel-first error design, wrapping rules, errors.Is/As contracts.258259- [references/CONFIG.md](references/CONFIG.md)260 Canonical Config struct patterns, constructor validation + defaults.261262- [references/INTERFACES.md](references/INTERFACES.md)263 Interface boundaries, driver patterns, testability-first design.264265- [references/LAYOUT.md](references/LAYOUT.md)266 File organization, struct field efficiency, package naming guidance.267268- [references/BENCHMARKS.md](references/BENCHMARKS.md)269 Benchmark expectations, templates, performance validation rules.270271- [references/REVIEW-CHECKLIST.md](references/REVIEW-CHECKLIST.md)272 PR review rubric for humans and coding agents.273274---275> Converted and distributed by [TomeVault](https://tomevault.io/claim/madflojo) — claim your Tome and manage your conversions.276<!-- tomevault:4.0:skill_md:2026-04-15 -->