Use when writing, reviewing, or modifying Go code (*.go, go.mod, go.sum files) or reviewing diffs/PRs containing Go changes — ensures code quality and adherence to the established Go conventions for naming, error handling, concurrency, types, logging, and code organization
type UserID uint64, not bare uint64). Typed constants with iota. Use generics instead of any/interface{}.Multi-parameter functions: each parameter on its own line, closing ) returnType { on separate line:
func (n *NodeWithCustomData[C, T]) RemovePushTo(
ctx context.Context,
to Abstract,
) (_err error) {
Unwrap() error.var errs []error, return errors.Join(errs...).switch {
case err == nil:
case errors.As(err, &ErrNotImplemented{}):
logger.Warnf(ctx, "...")
default:
return fmt.Errorf("...: %w", err)
}
func (c *Codec) Close(ctx context.Context) (_err error) {
logger.Tracef(ctx, "Close")
defer func() { logger.Tracef(ctx, "/Close: %v", _err) }()
defer func() {
if _err != nil {
_ = c.Close(ctx)
}
}()
context.Context is always the first parameter. Never stored in structs.observability.Go(ctx, func(ctx context.Context) { ... }), never raw go.defer. Prefer small functions that lock/defer-unlock at the top, rather than locking inside complex functions with multiple code paths.type Option interface { apply(*Config) }
type Options []Option
func (opts Options) config() Config { cfg := defaultConfig(); opts.apply(&cfg); return cfg }
type optionCheckInterval time.Duration
func (o optionCheckInterval) apply(c *Config) { c.CheckInterval = (time.Duration)(o) }
else if. Always use switch if semantically there could be more then 2 options. Even if in practice you currently have 1-2 options, but semantically there could be more, it still should be a switch.github.com/facebookincubator/go-belt via context: logger.Debugf(ctx, "...").context.Context. Never store a logger in a struct.belt.WithField(ctx, "key", value).logger.Tracef(ctx, "MethodName") / logger.Tracef(ctx, "/MethodName").main package.Trace — method entry/exit, low-level flow tracing.Debug — normal operational messages, state changes, request handling.Info — rare, notable events only (startup, shutdown, config reload). Most messages should be Debug, not Info.Warn — recoverable problems, degraded operation, unexpected-but-handled conditions.Error — operation failed, needs attention but process continues.Fatal — unrecoverable, process must exit.github.com/stretchr/testify — assert for soft, require for fatal.const block at file top, not magic values inline.how-it-works explanations and TODOs. No "generated by AI" or similar attributions.x > y silently accepts cases you didn't consider — use explicit checks for each supported case and error on the rest.A name is a contract — implementation fulfills exactly what the name promises.
resolveTable resolves a table — not decide whether to, retry, or log analytics. Extra behavior belongs in the caller or the name.ValidateAndSave must validate and save. If either can happen without the other, split or rename.disable must not return an "enabled" state. remove must not archive.GetUser → User. IsValid → bool. ListItems → collection.doX() assumes X should happen. "If not needed, return early" inside it is a violation — the caller decides.Is, Has, Can) don't change state. If they must, the name must reveal it.foocli (standalone tool) must not contain code requiring a running food daemon.Review check: read the name, predict the body, read the body. Any surprise is a violation.
Same concept → same name everywhere. Same name → same meaning everywhere. Related concepts → parallel structure.
Handle can't mean "process a request" here and "resource reference" there.StartCapture/StopCapture — not BeginEncoding/EndEncoding. Pick one verb set per domain.initializeCluster, configureNetwork, go — the last one breaks the level.StreamProcessor over MapWithMutex. Name must survive an implementation change.Everything as local as possible, as short-lived as possible.
:= in if/for/switch init statements.var is global state. Use only when multiple functions genuinely share it.replace directives (e.g., => ../something) to go.mod. Use go.work for local module resolution instead. Remote fork replacements in go.mod are fine.if/for/switch blocks. Use early returns, continue, and guard clauses to reduce nesting. If a block is nested 3+ levels deep, refactor it.