Skip to content

Latest commit

 

History

History
139 lines (108 loc) · 8.94 KB

File metadata and controls

139 lines (108 loc) · 8.94 KB
name golang-expert
description Idiomatic Go best practices for writing clean, performant, and maintainable Go code. Apply when writing, reviewing, or refactoring any Go code — including modules, packages, error handling, concurrency, CLI tools, file I/O, security, and tooling.

Golang Expert

Comprehensive guide to idiomatic Go, covering language best practices, design patterns, concurrency, CLI tool creation, file I/O, security, testing, and tooling. Apply these rules whenever writing or reviewing Go code to ensure correctness, clarity, and long-term maintainability.

When to Apply

Reference these guidelines when:

  • Writing new Go packages, modules, or services
  • Reviewing Go code for correctness and idiomatic style
  • Refactoring existing Go code
  • Designing interfaces and package APIs
  • Writing concurrent code with goroutines or channels
  • Building CLI tools or command-line interfaces
  • Handling file I/O, streaming, or file transfer
  • Implementing secure Go code
  • Writing tests or benchmarks
  • Setting up Go tooling in CI/CD pipelines

Rule Categories by Priority

Priority Category Impact Prefix
1 Code Organisation CRITICAL org-
2 Error Handling CRITICAL error-
3 Security CRITICAL sec-
4 Interfaces & Composition HIGH iface-
5 Concurrency HIGH conc-
6 CLI Tool Creation HIGH cli-
7 File I/O & Strings HIGH io-
8 Testing MEDIUM-HIGH test-
9 Performance MEDIUM perf-
10 Tooling & Linting MEDIUM tool-

Quick Reference

1. Code Organisation (CRITICAL)

  • org-module-structure — one module per repository; go.mod at the root
  • org-package-naming — short, lowercase, singular nouns; no underscores or mixedCase (user, not userService)
  • org-package-cohesion — organise packages by domain concept, not by layer (user/, not models/, controllers/, services/)
  • org-internal — use internal/ to prevent external packages from importing unexported APIs
  • org-cmd — keep main packages thin in cmd/<name>/main.go; all logic lives in importable packages

2. Error Handling (CRITICAL)

  • error-explicit — never ignore an error; assign to _ only when intentional and documented
  • error-wrapping — add context with fmt.Errorf("doing X: %w", err); preserve the original error for errors.Is/errors.As
  • error-sentinel — define sentinel errors with var ErrFoo = errors.New("foo") for values callers compare against
  • error-types — use custom error types (implementing error) when callers need to inspect structured error data
  • error-no-panic — do not use panic for recoverable errors; reserve it for truly unrecoverable programmer mistakes

3. Security (CRITICAL)

  • sec-no-hardcoded-secrets — never hardcode secrets, tokens, or passwords; read from environment variables or a secrets manager
  • sec-crypto-rand — use crypto/rand for all security-sensitive random values; never use math/rand for tokens or nonces
  • sec-constant-time — compare secrets and tokens with subtle.ConstantTimeCompare to prevent timing attacks
  • sec-tls-config — always use tls.Config with MinVersion: tls.VersionTLS12; never set InsecureSkipVerify: true in production
  • sec-exec-no-shell — use exec.Command("bin", arg1, arg2) with explicit args; never interpolate user input into a shell string
  • sec-sql-parameterised — always use parameterised queries (db.QueryContext(ctx, "SELECT ... WHERE id=?", id)); never concatenate SQL strings
  • sec-input-validation — validate and sanitise all external input at the boundary; fail fast with a clear error

4. Interfaces & Composition (HIGH)

  • iface-small — prefer single-method interfaces (io.Reader, io.Writer); the smaller the interface, the more implementations it accepts
  • iface-accept-return — accept interfaces, return concrete types; this maximises flexibility for callers
  • iface-define-at-use — define interfaces in the package that uses them, not the package that implements them
  • iface-composition — compose larger interfaces from smaller ones rather than defining monolithic interfaces

5. Concurrency (HIGH)

  • conc-context — accept context.Context as the first parameter of any function that does I/O or can be cancelled; never store context in a struct
  • conc-goroutine-cleanup — every goroutine must have a clear owner and a defined exit path; use sync.WaitGroup or errgroup to wait for completion
  • conc-channel-ownership — the goroutine that creates a channel is responsible for closing it; never close a channel from the receiver side
  • conc-mutex-vs-channel — use channels for transferring ownership or signalling; use sync.Mutex for protecting shared state
  • conc-race-detector — always run tests with -race; enable it in CI (go test -race ./...)

6. CLI Tool Creation (HIGH)

  • cli-cobra-structure — use cobra for multi-command CLIs; one *cobra.Command per file under cmd/; root command in cmd/root.go
  • cli-flag-validation — validate all flags in PersistentPreRunE or RunE; return an error rather than calling os.Exit directly
  • cli-exit-codes — exit 0 on success, 1 on user error, 2 on internal/unexpected error; use os.Exit only in main
  • cli-stderr-stdout — write human-readable output to stdout; write errors, warnings, and progress to stderr
  • cli-embed-config — use //go:embed to bundle default config templates, completion scripts, or static assets into the binary
  • cli-heredoc-templates — use raw string literals (backticks) with text/template for multi-line output templates; avoid hand-built string concatenation
  • cli-shell-completion — generate shell completion scripts via cobra completion; add a completion subcommand

7. File I/O & Strings (HIGH)

  • io-stream-not-buffer — use io.Copy to stream file content; never read an entire file into memory with os.ReadFile unless it is provably small
  • io-buffered-rw — wrap file reads/writes in bufio.NewReader/bufio.NewWriter; flush writers explicitly with defer w.Flush()
  • io-atomic-write — write to a temp file in the same directory, then rename; this prevents partial writes corrupting the destination
  • io-close-defer — always defer f.Close() immediately after a successful os.Open; check the error on Close for writers
  • io-filepath-not-path — use path/filepath (not path) for OS file paths; use path only for URL path segments
  • io-heredoc-raw-strings — use raw string literals (`) for multi-line strings, SQL, JSON templates, and scripts; avoid escape-heavy interpreted strings
  • io-embed-assets — use //go:embed to include static files, templates, and schemas in the binary at compile time

8. Testing (MEDIUM-HIGH)

  • test-table-driven — use table-driven tests ([]struct{ name, input, want }) for all non-trivial logic
  • test-subtests — use t.Run(tc.name, func(t *testing.T) {...}) inside table-driven loops for isolated, named failures
  • test-interface-mocking — mock dependencies via interfaces, not concrete types or monkey-patching
  • test-golden-files — use golden files (testdata/*.golden) for complex expected outputs; update with -update flag
  • test-benchmarks — write BenchmarkXxx functions for performance-critical paths; run with go test -bench=.

9. Performance (MEDIUM)

  • perf-avoid-allocations — profile with pprof before optimising; minimise heap allocations in hot paths using sync.Pool or pre-allocated slices
  • perf-strings-builder — use strings.Builder (or bytes.Buffer) for string concatenation in loops; never use += in a loop
  • perf-slice-capacity — pre-allocate slices with make([]T, 0, n) when the final size is known
  • perf-struct-layout — order struct fields from largest to smallest alignment to minimise padding

10. Tooling & Linting (MEDIUM)

  • tool-gofmt — always run gofmt -s (or goimports); enforce in CI with gofmt -l . exiting non-zero on diff
  • tool-go-vet — run go vet ./... as part of every CI build; it catches correctness issues gofmt misses
  • tool-golangci-lint — use golangci-lint run in CI with a committed .golangci.yml; enable at minimum errcheck, staticcheck, gosimple, unused
  • tool-go-generate — use //go:generate directives for code generation; commit generated files so the build does not require external tools at runtime

How to Use

Apply rules by ID when reviewing or writing Go code. Read individual rule files in references/ for detailed explanations and before/after code examples:

references/error-handling-patterns.md
references/security-patterns.md
references/concurrency-patterns.md
references/cli-patterns.md
references/io-and-strings-patterns.md
references/interface-patterns.md
references/testing-patterns.md
references/code-organisation-patterns.md

See references/rule-index.md for the full list of all rules mapped to their local files.