This skill defines practical Go engineering conventions for humans, coding agents, and production-ready systems.
Use this skill whenever you are working with Go: new code, refactors, reviews, architecture decisions, package design, or test strategy.
TL;DR
- Design for testability first; inject dependencies and keep logic pure.
- Prefer
Configin -> concrete struct out; validate, default, and document important runtime knobs. - Errors are contracts: use sentinels for durable branching; wrap the rest with
%worerrors.Join. - Keep packages reusable: no hidden globals, no default logging, no surprise side effects.
- Prefer the standard library before adding dependencies; third-party packages must earn their weight through meaningful, maintained, adopted abstraction.
- Coverage is a signal, not proof; test edge cases and misuse paths, not just happy paths.
- Follow "accept interfaces, return structs"; consumers usually define interfaces, shared contract packages are a special case.
- Keep
main.gothin; follow existing repo layout conventions rather than forcing one directory shape. - Benchmark hot paths before claiming wins, and run concurrency code with
-race. - Maintain contracts such as function signatures, config shape, error behavior, and doc comments; they are as important as the code itself.
House Style Disclaimer
This is intentionally opinionated. It favors consistency and long-term maintainability over accommodating every Go style preference. If the target repo has clear local conventions, those conventions usually win.
Compatibility
Examples assume modern Go and use standard-library features such as
errors.Join and log/slog. Apply the guide within the constraints of the
target repository's supported Go version.
Execution Protocol
Follow this workflow when using the skill for implementation work:
-
Inspect the repository first. Read existing package layout, constructors, tests, and error conventions before proposing new APIs or moving files.
-
Define the contract before coding. Decide the package boundary, config shape, concrete return type, sentinel errors, dependency seams, and context or shutdown expectations up front.
-
Write or update tests early when practical. Start with table-driven unit tests, add fuzz tests for parsing or other input-heavy code, and add benchmarks for performance-sensitive paths.
-
Implement the smallest maintainable change. Follow the repository's existing layout, keep
main.gothin, and avoid introducing new abstractions without a clear boundary. -
Run the finishing checks. Format with
gofmt(andgoimportsif the repo uses it), run the relevantgo testtargets, rungo test -racewhen concurrency is involved, and run benchmarks when claiming performance improvements. -
Verify the human-facing contract. Make sure docs, comments, config defaults, and error behavior match the code you are shipping.
Quick Rules Table
| Topic | Rule | Reference |
|---|---|---|
| Testability | Design for confidence, not coverage percentages; test edge and misuse cases | references/TESTING.md |
| Constructors | Config in -> concrete struct out; validate + default in New; use Config.Validate() when config logic grows |
references/CONFIG.md |
| Errors | Use sentinels for durable branching; wrap with %w or errors.Join; keep recover at app boundaries |
references/ERRORS.md |
| Logging | Packages do not log by default; hot-path logging is a performance decision | references/LOGGING.md |
| Dependencies | Standard library first; add third-party packages only when they provide meaningful, maintained value | references/LAYOUT.md |
| Interfaces | "Accept interfaces, return structs"; consumers usually define interfaces | references/INTERFACES.md |
| Documentation | Write idiomatic godoc and durable comments; never add agent-context comments | references/DOCUMENTATION.md |
| Layout | Keep packages shallow, avoid junk drawers, and follow repo conventions | references/LAYOUT.md |
| Entry Points | main.go is wiring only |
references/LAYOUT.md |
| Benchmarks | Benchmark hot paths; use b.ReportAllocs() and compare runs with benchstat |
references/BENCHMARKS.md |
| Testing | Table-driven, stdlib-first, defensive against misuse, and fuzz-heavy where inputs are complex | references/TESTING.md |
| Concurrency | Every goroutine needs a shutdown path; use context.Context, -race, and jitter where needed |
references/CONCURRENCY.md |
| Reviews | Use the checklist when reviewing Go changes | references/REVIEW-CHECKLIST.md |
Common Pitfalls
- Returning interfaces by default instead of concrete types.
- Treating coverage percentages as proof of correctness.
- Logging in reusable packages instead of returning errors.
- Adding frameworks or helper libraries when the standard library is already clear enough.
- Passing global app config through packages rather than local
Config. - Leaving critical runtime knobs on dangerous defaults.
- Forcing a house directory layout onto repos that already have clear conventions.
- Loading every reference document before you know which topic the task touches.
- Shipping changes that claim performance wins without benchmarks or
concurrency safety without
-race.
Core Guidance
Load reference files only when the task needs that topic's detail.
- App packages own dependency wiring, lifecycle, error policy, logging, and metrics policy.
- Reusable packages return errors, define local
ConfigorOpts, accept initialized dependencies, and avoid hidden logging or global state. - Dependency choices should start with the standard library. Reach for a third-party package only when it solves a real problem, is actively maintained, and earns its transitive cost.
- Services usually keep
cmd/<appname>/main.gothin. Follow established repo layout over forcingpkg/,internal/, or any house shape. - Libraries usually keep packages shallow and domain-focused.
- Constructors prefer
New(cfg Config) (*T, error), explicit defaults, validation, concrete returns, and immutable normalized config. - Interfaces should be small, boundary-driven, and usually consumer-defined.
- Tests should be table-driven, stdlib-first, defensive, and run with
-racefor concurrency-sensitive code. - Performance claims need benchmarks.
- Docs, comments, config defaults, error behavior, and signatures are part of the contract.
Reference Loading
Load only the references needed for the active task:
- Read references/CONFIG.md for constructors,
Configstructs, defaults, validation, degraded modes, or logging injection. - Read references/INTERFACES.md for interface boundaries, driver patterns, mocks/fakes, or concrete return decisions.
- Read references/ERRORS.md for sentinel errors, wrapping, typed errors, recover placement, or constructor failures.
- Read references/LOGGING.md for package logging,
slog, log levels, async error surfaces, or payload safety. - Read references/TESTING.md for table-driven tests,
fuzzing,
testdata, integration gates, test helpers, or assertion choices. - Read references/CONCURRENCY.md for goroutines, context propagation, graceful shutdown, mutex/atomic/channel choices, jitter, or race testing.
- Read references/LAYOUT.md for package layout, file
naming,
main.go, dependency hygiene, struct field layout, or export rules. - Read references/DOCUMENTATION.md for package docs, godoc, examples, deprecations, internal comments, or field docs.
- Read references/BENCHMARKS.md for benchmark
shape, allocation reporting, concurrent benchmarks, IO benchmarks, or
benchstat. - Read references/REVIEW-CHECKLIST.md when reviewing a Go PR or doing a final self-check.