Go Standards
Severity System
- MUST -- Enforced by CI/review. Violations block merge.
- SHOULD -- Strong recommendations. Deviations require rationale in PR.
- CAN -- Allowed without extra approval. Use when beneficial.
1 -- Before Coding
- BP-1 (MUST) Ask clarifying questions for ambiguous requirements.
- BP-2 (MUST) Draft and confirm an approach (API shape, data flow, failure modes) before writing code.
- BP-3 (SHOULD) When >2 approaches exist, list pros/cons and rationale.
- BP-4 (SHOULD) Define testing strategy (unit/integration) and observability signals up front.
2 -- Modules and Dependencies
- MD-1 (SHOULD) Prefer stdlib; introduce deps only with clear payoff; track transitive size and licenses.
- MD-2 (CAN) Use
govulncheck for dependency auditing and updates.
3 -- Code Style
- CS-1 (MUST) Enforce
gofmt and go vet on all code.
- CS-2 (MUST) Avoid stutter in names:
package kv; type Store not KVStore in kv.
- CS-3 (SHOULD) Small interfaces near consumers; prefer composition over inheritance.
- CS-4 (SHOULD) Avoid reflection on hot paths; prefer generics when it clarifies and speeds.
- CS-5 (MUST) Use input structs for functions receiving more than 2 arguments. Context is always a separate first parameter, never in the input struct.
- CS-6 (SHOULD) Declare function input structs immediately before the function that consumes them.
- CS-7 (MUST) Use
any instead of interface{}.
- CS-8 (SHOULD) Avoid
else; use early return, continue, or break.
- CS-9 (MUST) Import grouping order: stdlib, third-party, org, local. Separate with blank lines.
- CS-10 (SHOULD) Reduce nesting with early returns. Handle the error case first, then continue with the happy path.
- CS-11 (SHOULD) Reduce scope of variables; declare closest to first use.
- CS-12 (SHOULD) Use raw string literals to avoid escaping.
- CS-13 (MUST) Use field names in struct initialization (no positional).
- CS-14 (SHOULD) Group similar declarations together.
- CS-15 (SHOULD) Prefer
strconv over fmt for string/number conversions.
- CS-16 (SHOULD) Nil is a valid slice; don't return empty slices just to avoid nil.
- CS-17 (SHOULD) Use
"time" package types properly; never use raw int for durations.
- CS-18 (SHOULD) Use field tags in marshaled structs (JSON, TOML, etc.).
// CS-5: Input struct for 3+ arguments (context stays outside)
type WalkInput struct {
Root string
MaxDepth int
IgnoreFile string
}
func Walk(ctx context.Context, in WalkInput) ([]FileDescriptor, error) {
// ...
}
// CS-9: Import ordering
import (
"context"
"fmt"
"os"
"github.com/spf13/cobra"
"golang.org/x/sync/errgroup"
"github.com/raven/raven/internal/config"
"github.com/raven/raven/internal/discovery"
)
// CS-8: Early return instead of else
func process(fd FileDescriptor) error {
if fd.IsBinary {
return ErrSkipped
}
// happy path continues at top level
return nil
}
4 -- Errors
- ERR-1 (MUST) Wrap with
%w and context: fmt.Errorf("open %s: %w", path, err).
- ERR-2 (MUST) Use
errors.Is/errors.As for control flow; never match error strings.
- ERR-3 (SHOULD) Define sentinel errors in the package; document their behavior.
- ERR-4 (CAN) Use
context.WithCancelCause and context.Cause for propagating error causes.
- ERR-5 (MUST) Handle errors once: log OR return, never both.
- ERR-6 (MUST) Do not panic; return errors instead. Panics are reserved for truly unrecoverable programmer bugs.
- ERR-7 (SHOULD) Prefer
errors.New for simple static errors; custom types for errors needing fields.
- ERR-8 (SHOULD) Use
%w when callers need Is/As; use %v to intentionally break the error chain.
- ERR-9 (MUST) Handle type assertion failures with comma-ok pattern.
// ERR-1 + ERR-3: Sentinel errors and wrapping
var (
ErrSkipped = errors.New("file skipped")
ErrBudgetExc = errors.New("token budget exceeded")
)
func processFile(path string) error {
data, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("read %s: %w", path, err)
}
// ...
return nil
}
// ERR-9: Type assertion with comma-ok
val, ok := msg.(tea.KeyMsg)
if !ok {
return m, nil
}
5 -- Concurrency
- CC-1 (MUST) The sender closes channels; receivers never close.
- CC-2 (MUST) Tie goroutine lifetime to a
context.Context; prevent leaks.
- CC-3 (MUST) Protect shared state with
sync.Mutex/atomic; no "probably safe" races.
- CC-4 (SHOULD) Use
errgroup for fan-out work; cancel on first error.
- CC-5 (CAN) Prefer buffered channels only with documented rationale (throughput/back-pressure).
- CC-6 (MUST) Never fire-and-forget goroutines. Every goroutine must be tracked and joined.
// CC-2 + CC-4: errgroup with context cancellation
func walkParallel(ctx context.Context, paths []string) ([]FileDescriptor, error) {
g, ctx := errgroup.WithContext(ctx)
g.SetLimit(runtime.NumCPU())
var mu sync.Mutex
var results []FileDescriptor
for _, p := range paths {
g.Go(func() error {
fd, err := processPath(ctx, p)
if err != nil {
return fmt.Errorf("process %s: %w", p, err)
}
mu.Lock()
results = append(results, fd)
mu.Unlock()
return nil
})
}
if err := g.Wait(); err != nil {
return nil, err
}
return results, nil
}
6 -- Contexts
- CTX-1 (MUST)
ctx context.Context is always the first parameter; never store ctx in structs.
- CTX-2 (MUST) Propagate non-nil
ctx; honor Done, deadlines, and timeouts.
- CTX-3 (CAN) Expose
WithX(ctx) helpers that derive deadlines from config.
// CTX-1: Context is always first, before the input struct
func (s *Scanner) Scan(ctx context.Context, in ScanInput) ([]Finding, error) {
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}
// proceed with scan...
}
7 -- Testing
- T-1 (MUST) Table-driven tests; deterministic and hermetic by default.
- T-2 (MUST) Run
-race in CI; add t.Cleanup for teardown.
- T-3 (SHOULD) Mark safe tests with
t.Parallel().
- T-4 (SHOULD) Split success and error test paths into separate test functions.
- T-5 (SHOULD) Use testify:
require for fatal assertions, assert for soft checks.
- T-6 (MUST) Document only public interfaces, types, functions, methods.
// T-1 + T-5: Table-driven test with testify
func TestTokenizer_Count(t *testing.T) {
tests := []struct {
name string
content string
want int
}{
{name: "empty", content: "", want: 0},
{name: "single word", content: "hello", want: 1},
{name: "go function", content: "func main() {}", want: 5},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got, err := tokenizer.Count(tt.content)
require.NoError(t, err)
assert.Equal(t, tt.want, got)
})
}
}
8 -- Logging and Observability
- OBS-1 (MUST) Structured logging with
slog; use levels and consistent field names.
- OBS-2 (SHOULD) Correlate logs, metrics, and traces via request IDs from context.
slog.Info("pipeline stage complete",
"stage", "discovery",
"files", len(results),
"duration", elapsed,
)
9 -- Performance
- PERF-1 (MUST) Measure before optimizing:
pprof, go test -bench, benchstat.
- PERF-2 (SHOULD) Avoid allocations on hot paths; reuse buffers; prefer
bytes/strings APIs.
- PERF-3 (CAN) Add microbenchmarks for critical functions and track regressions in CI.
- PERF-4 (SHOULD) Specify container capacity; pre-allocate slices and maps when size is known.
// PERF-4: Pre-allocate with known capacity
files := make([]FileDescriptor, 0, len(paths))
tierMap := make(map[string]int, len(files))
10 -- Configuration
- CFG-1 (MUST) Config via env/flags; validate on startup; fail fast on invalid config.
- CFG-2 (MUST) Treat config as immutable after init; pass explicitly, not via globals.
- CFG-3 (SHOULD) Provide sane defaults and clear documentation.
11 -- APIs and Boundaries
- API-1 (MUST) Document exported items:
// Foo does ...; keep exported surface minimal.
- API-2 (MUST) Accept interfaces where variation is needed; return concrete types unless abstraction is required.
- API-3 (SHOULD) Keep functions small, orthogonal, and composable.
- API-4 (SHOULD) Avoid embedding types in public structs (leaks implementation).
- API-5 (CAN) Use functional options pattern for extensibility.
- API-6 (MUST) Verify interface compliance at compile time.
- API-7 (SHOULD) Avoid mutable globals. If unavoidable, protect with mutex.
- API-8 (SHOULD) Avoid
init(). Prefer explicit initialization.
- API-9 (MUST) Exit only in
main(). All other code returns errors.
// API-6: Compile-time interface compliance
var _ discovery.Walker = (*FileWalker)(nil)
// API-5: Functional options
type Option func(*Server)
func WithLogger(l *slog.Logger) Option {
return func(s *Server) { s.logger = l }
}
func WithTimeout(d time.Duration) Option {
return func(s *Server) { s.timeout = d }
}
func NewServer(addr string, opts ...Option) *Server {
s := &Server{addr: addr, timeout: 30 * time.Second}
for _, opt := range opts {
opt(s)
}
return s
}
12 -- Security
- SEC-1 (MUST) Validate inputs; set explicit I/O timeouts; prefer TLS everywhere.
- SEC-2 (MUST) Never log secrets; manage secrets outside code (env/secret manager).
- SEC-3 (SHOULD) Limit filesystem/network access by default; principle of least privilege.
- SEC-4 (CAN) Add fuzz tests for untrusted inputs (redaction patterns, config parsing).
13 -- CI/CD
- CI-1 (MUST) Lint, vet, test (
-race), and build on every PR; cache modules/builds.
- CI-2 (MUST) Reproducible builds with
-trimpath; embed version via -ldflags "-X main.version=$TAG".
14 -- Tooling Gates
- G-1 (MUST)
go vet ./... passes.
- G-2 (MUST)
golangci-lint run passes with project config.
- G-3 (MUST)
go test -race ./... passes.
15 -- Defensive Coding
- DC-1 (MUST) Copy slices and maps at package boundaries; do not hold references to caller data.
- DC-2 (SHOULD) Zero-value mutexes are valid; never use pointer to mutex.
- DC-3 (SHOULD) Defer to clean up resources (files, locks, connections).
- DC-4 (SHOULD) Channel size is one or none; document rationale for larger buffers.
- DC-5 (SHOULD) Start enums at one; reserve zero value for "unset".
// DC-1: Copy slices at boundaries
func (c *Config) SetIgnorePatterns(patterns []string) {
c.patterns = make([]string, len(patterns))
copy(c.patterns, patterns)
}
// DC-5: Enums start at one
type Tier int
const (
TierUnset Tier = iota // 0 = unset/invalid
TierCritical // 1
TierImportant // 2
TierNormal // 3
)
Converted and distributed by TomeVault — claim your Tome and manage your conversions.
1---2name: go-standards-23description: Go coding standards with MUST/SHOULD/CAN severity. Enforces idiomatic Go, error handling, concurrency safety, testing, and security. Reference for all Go code in Raven. Use when this capability is needed.4---56# Go Standards78## Severity System910- **MUST** -- Enforced by CI/review. Violations block merge.11- **SHOULD** -- Strong recommendations. Deviations require rationale in PR.12- **CAN** -- Allowed without extra approval. Use when beneficial.1314---1516## 1 -- Before Coding1718- **BP-1 (MUST)** Ask clarifying questions for ambiguous requirements.19- **BP-2 (MUST)** Draft and confirm an approach (API shape, data flow, failure modes) before writing code.20- **BP-3 (SHOULD)** When >2 approaches exist, list pros/cons and rationale.21- **BP-4 (SHOULD)** Define testing strategy (unit/integration) and observability signals up front.2223## 2 -- Modules and Dependencies2425- **MD-1 (SHOULD)** Prefer stdlib; introduce deps only with clear payoff; track transitive size and licenses.26- **MD-2 (CAN)** Use `govulncheck` for dependency auditing and updates.2728## 3 -- Code Style2930- **CS-1 (MUST)** Enforce `gofmt` and `go vet` on all code.31- **CS-2 (MUST)** Avoid stutter in names: `package kv; type Store` not `KVStore` in `kv`.32- **CS-3 (SHOULD)** Small interfaces near consumers; prefer composition over inheritance.33- **CS-4 (SHOULD)** Avoid reflection on hot paths; prefer generics when it clarifies and speeds.34- **CS-5 (MUST)** Use input structs for functions receiving more than 2 arguments. Context is always a separate first parameter, never in the input struct.35- **CS-6 (SHOULD)** Declare function input structs immediately before the function that consumes them.36- **CS-7 (MUST)** Use `any` instead of `interface{}`.37- **CS-8 (SHOULD)** Avoid `else`; use early return, continue, or break.38- **CS-9 (MUST)** Import grouping order: stdlib, third-party, org, local. Separate with blank lines.39- **CS-10 (SHOULD)** Reduce nesting with early returns. Handle the error case first, then continue with the happy path.40- **CS-11 (SHOULD)** Reduce scope of variables; declare closest to first use.41- **CS-12 (SHOULD)** Use raw string literals to avoid escaping.42- **CS-13 (MUST)** Use field names in struct initialization (no positional).43- **CS-14 (SHOULD)** Group similar declarations together.44- **CS-15 (SHOULD)** Prefer `strconv` over `fmt` for string/number conversions.45- **CS-16 (SHOULD)** Nil is a valid slice; don't return empty slices just to avoid nil.46- **CS-17 (SHOULD)** Use `"time"` package types properly; never use raw `int` for durations.47- **CS-18 (SHOULD)** Use field tags in marshaled structs (JSON, TOML, etc.).4849```go50// CS-5: Input struct for 3+ arguments (context stays outside)51type WalkInput struct {52 Root string53 MaxDepth int54 IgnoreFile string55}5657func Walk(ctx context.Context, in WalkInput) ([]FileDescriptor, error) {58 // ...59}6061// CS-9: Import ordering62import (63 "context"64 "fmt"65 "os"6667 "github.com/spf13/cobra"68 "golang.org/x/sync/errgroup"6970 "github.com/raven/raven/internal/config"71 "github.com/raven/raven/internal/discovery"72)7374// CS-8: Early return instead of else75func process(fd FileDescriptor) error {76 if fd.IsBinary {77 return ErrSkipped78 }79 // happy path continues at top level80 return nil81}82```8384## 4 -- Errors8586- **ERR-1 (MUST)** Wrap with `%w` and context: `fmt.Errorf("open %s: %w", path, err)`.87- **ERR-2 (MUST)** Use `errors.Is`/`errors.As` for control flow; never match error strings.88- **ERR-3 (SHOULD)** Define sentinel errors in the package; document their behavior.89- **ERR-4 (CAN)** Use `context.WithCancelCause` and `context.Cause` for propagating error causes.90- **ERR-5 (MUST)** Handle errors once: log OR return, never both.91- **ERR-6 (MUST)** Do not panic; return errors instead. Panics are reserved for truly unrecoverable programmer bugs.92- **ERR-7 (SHOULD)** Prefer `errors.New` for simple static errors; custom types for errors needing fields.93- **ERR-8 (SHOULD)** Use `%w` when callers need `Is`/`As`; use `%v` to intentionally break the error chain.94- **ERR-9 (MUST)** Handle type assertion failures with comma-ok pattern.9596```go97// ERR-1 + ERR-3: Sentinel errors and wrapping98var (99 ErrSkipped = errors.New("file skipped")100 ErrBudgetExc = errors.New("token budget exceeded")101)102103func processFile(path string) error {104 data, err := os.ReadFile(path)105 if err != nil {106 return fmt.Errorf("read %s: %w", path, err)107 }108 // ...109 return nil110}111112// ERR-9: Type assertion with comma-ok113val, ok := msg.(tea.KeyMsg)114if !ok {115 return m, nil116}117```118119## 5 -- Concurrency120121- **CC-1 (MUST)** The sender closes channels; receivers never close.122- **CC-2 (MUST)** Tie goroutine lifetime to a `context.Context`; prevent leaks.123- **CC-3 (MUST)** Protect shared state with `sync.Mutex`/`atomic`; no "probably safe" races.124- **CC-4 (SHOULD)** Use `errgroup` for fan-out work; cancel on first error.125- **CC-5 (CAN)** Prefer buffered channels only with documented rationale (throughput/back-pressure).126- **CC-6 (MUST)** Never fire-and-forget goroutines. Every goroutine must be tracked and joined.127128```go129// CC-2 + CC-4: errgroup with context cancellation130func walkParallel(ctx context.Context, paths []string) ([]FileDescriptor, error) {131 g, ctx := errgroup.WithContext(ctx)132 g.SetLimit(runtime.NumCPU())133134 var mu sync.Mutex135 var results []FileDescriptor136137 for _, p := range paths {138 g.Go(func() error {139 fd, err := processPath(ctx, p)140 if err != nil {141 return fmt.Errorf("process %s: %w", p, err)142 }143 mu.Lock()144 results = append(results, fd)145 mu.Unlock()146 return nil147 })148 }149150 if err := g.Wait(); err != nil {151 return nil, err152 }153 return results, nil154}155```156157## 6 -- Contexts158159- **CTX-1 (MUST)** `ctx context.Context` is always the first parameter; never store ctx in structs.160- **CTX-2 (MUST)** Propagate non-nil `ctx`; honor `Done`, deadlines, and timeouts.161- **CTX-3 (CAN)** Expose `WithX(ctx)` helpers that derive deadlines from config.162163```go164// CTX-1: Context is always first, before the input struct165func (s *Scanner) Scan(ctx context.Context, in ScanInput) ([]Finding, error) {166 select {167 case <-ctx.Done():168 return nil, ctx.Err()169 default:170 }171 // proceed with scan...172}173```174175## 7 -- Testing176177- **T-1 (MUST)** Table-driven tests; deterministic and hermetic by default.178- **T-2 (MUST)** Run `-race` in CI; add `t.Cleanup` for teardown.179- **T-3 (SHOULD)** Mark safe tests with `t.Parallel()`.180- **T-4 (SHOULD)** Split success and error test paths into separate test functions.181- **T-5 (SHOULD)** Use testify: `require` for fatal assertions, `assert` for soft checks.182- **T-6 (MUST)** Document only public interfaces, types, functions, methods.183184```go185// T-1 + T-5: Table-driven test with testify186func TestTokenizer_Count(t *testing.T) {187 tests := []struct {188 name string189 content string190 want int191 }{192 {name: "empty", content: "", want: 0},193 {name: "single word", content: "hello", want: 1},194 {name: "go function", content: "func main() {}", want: 5},195 }196 for _, tt := range tests {197 t.Run(tt.name, func(t *testing.T) {198 t.Parallel()199 got, err := tokenizer.Count(tt.content)200 require.NoError(t, err)201 assert.Equal(t, tt.want, got)202 })203 }204}205```206207## 8 -- Logging and Observability208209- **OBS-1 (MUST)** Structured logging with `slog`; use levels and consistent field names.210- **OBS-2 (SHOULD)** Correlate logs, metrics, and traces via request IDs from context.211212```go213slog.Info("pipeline stage complete",214 "stage", "discovery",215 "files", len(results),216 "duration", elapsed,217)218```219220## 9 -- Performance221222- **PERF-1 (MUST)** Measure before optimizing: `pprof`, `go test -bench`, `benchstat`.223- **PERF-2 (SHOULD)** Avoid allocations on hot paths; reuse buffers; prefer `bytes`/`strings` APIs.224- **PERF-3 (CAN)** Add microbenchmarks for critical functions and track regressions in CI.225- **PERF-4 (SHOULD)** Specify container capacity; pre-allocate slices and maps when size is known.226227```go228// PERF-4: Pre-allocate with known capacity229files := make([]FileDescriptor, 0, len(paths))230tierMap := make(map[string]int, len(files))231```232233## 10 -- Configuration234235- **CFG-1 (MUST)** Config via env/flags; validate on startup; fail fast on invalid config.236- **CFG-2 (MUST)** Treat config as immutable after init; pass explicitly, not via globals.237- **CFG-3 (SHOULD)** Provide sane defaults and clear documentation.238239## 11 -- APIs and Boundaries240241- **API-1 (MUST)** Document exported items: `// Foo does ...`; keep exported surface minimal.242- **API-2 (MUST)** Accept interfaces where variation is needed; return concrete types unless abstraction is required.243- **API-3 (SHOULD)** Keep functions small, orthogonal, and composable.244- **API-4 (SHOULD)** Avoid embedding types in public structs (leaks implementation).245- **API-5 (CAN)** Use functional options pattern for extensibility.246- **API-6 (MUST)** Verify interface compliance at compile time.247- **API-7 (SHOULD)** Avoid mutable globals. If unavoidable, protect with mutex.248- **API-8 (SHOULD)** Avoid `init()`. Prefer explicit initialization.249- **API-9 (MUST)** Exit only in `main()`. All other code returns errors.250251```go252// API-6: Compile-time interface compliance253var _ discovery.Walker = (*FileWalker)(nil)254255// API-5: Functional options256type Option func(*Server)257258func WithLogger(l *slog.Logger) Option {259 return func(s *Server) { s.logger = l }260}261262func WithTimeout(d time.Duration) Option {263 return func(s *Server) { s.timeout = d }264}265266func NewServer(addr string, opts ...Option) *Server {267 s := &Server{addr: addr, timeout: 30 * time.Second}268 for _, opt := range opts {269 opt(s)270 }271 return s272}273```274275## 12 -- Security276277- **SEC-1 (MUST)** Validate inputs; set explicit I/O timeouts; prefer TLS everywhere.278- **SEC-2 (MUST)** Never log secrets; manage secrets outside code (env/secret manager).279- **SEC-3 (SHOULD)** Limit filesystem/network access by default; principle of least privilege.280- **SEC-4 (CAN)** Add fuzz tests for untrusted inputs (redaction patterns, config parsing).281282## 13 -- CI/CD283284- **CI-1 (MUST)** Lint, vet, test (`-race`), and build on every PR; cache modules/builds.285- **CI-2 (MUST)** Reproducible builds with `-trimpath`; embed version via `-ldflags "-X main.version=$TAG"`.286287## 14 -- Tooling Gates288289- **G-1 (MUST)** `go vet ./...` passes.290- **G-2 (MUST)** `golangci-lint run` passes with project config.291- **G-3 (MUST)** `go test -race ./...` passes.292293## 15 -- Defensive Coding294295- **DC-1 (MUST)** Copy slices and maps at package boundaries; do not hold references to caller data.296- **DC-2 (SHOULD)** Zero-value mutexes are valid; never use pointer to mutex.297- **DC-3 (SHOULD)** Defer to clean up resources (files, locks, connections).298- **DC-4 (SHOULD)** Channel size is one or none; document rationale for larger buffers.299- **DC-5 (SHOULD)** Start enums at one; reserve zero value for "unset".300301```go302// DC-1: Copy slices at boundaries303func (c *Config) SetIgnorePatterns(patterns []string) {304 c.patterns = make([]string, len(patterns))305 copy(c.patterns, patterns)306}307308// DC-5: Enums start at one309type Tier int310311const (312 TierUnset Tier = iota // 0 = unset/invalid313 TierCritical // 1314 TierImportant // 2315 TierNormal // 3316)317```318319---320> Converted and distributed by [TomeVault](https://tomevault.io/claim/abdelazizmoustafa10m) — claim your Tome and manage your conversions.321<!-- tomevault:4.0:skill_md:2026-04-15 -->