Files
skills-public/code-languages/go.md
T
BluscreamandClaude Opus 5 58525056a1 docs: use <repo>/<rel path> links throughout, document the convention
All cross-skill links now spell out the repository: skills-public/foo.md,
skills-private/bar.md. These resolve from the shared parent directory, so one
spelling works from either repo and from code-languages/ subdirectories —
replacing the previous mix of ./, ../ and absolute file:// URLs. Link labels
normalised to match. Convention added to the README conventions list.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 12:44:23 +02:00

158 lines
5.5 KiB
Markdown

---
name: go
description: >-
Use this skill when writing, building, linting, testing, or releasing Go — including
golangci-lint configuration, strict typing over map[string]interface{}, official SDK
preference, CGO-free static builds, context handling, and the build gate.
---
# Go — Rules & Preferences
Extends [`coding-dev.md`](skills-public/coding-dev.md). Everything there applies; this adds Go specifics.
---
## 1. The Gate
```bash
gofmt -l . # must print nothing
go vet ./...
golangci-lint run
go test -race ./...
go build ./...
```
Wire this into a single entry point (`make check`, `scripts/build.sh`) so it cannot be
partially run. **A skipped test fails the gate.** Add `govulncheck` for binary vulnerability
scanning on anything that ships.
---
## 2. Linting — Strict Set
`golangci-lint` with a deliberately broad selection, not the defaults:
```yaml
linters:
enable:
- staticcheck
- errcheck # every returned error is handled
- gosec # security findings
- noctx # no HTTP request without a context
- revive
- misspell
- unconvert
- bodyclose # every response body is closed
- goconst
- ineffassign
- nilerr
```
`errcheck` and `bodyclose` catch the two most common real Go bugs. As always: fix rather than
suppress, and when suppressing use a specific `//nolint:name // reason` with the reason.
---
## 3. Typing — Concrete, Not Dynamic
- **Strongly typed structs everywhere.** Avoid `map[string]interface{}` and reflection when a
struct can be defined — including for JSON, config, and API payloads.
- **Typed constants/enums**, not bare strings:
```go
type SyncStatus string
const (
StatusSynced SyncStatus = "synced"
StatusOutdated SyncStatus = "outdated"
StatusDirty SyncStatus = "dirty"
)
```
- **Concrete error types** with `errors.Is` / `errors.As`, not string comparison.
- Keep types shared with a frontend structurally in sync with that frontend's types; when both
exist, a change to one is incomplete without the other.
---
## 4. Errors
- **Every error is handled.** `errcheck` enforces it; do not `_ =` an error away without a
comment explaining why it cannot matter.
- Wrap with context: `fmt.Errorf("fetching %s: %w", name, err)`. Always `%w`, never `%v`, when
the caller might need to inspect it.
- Sentinel errors as package-level `var ErrNotFound = errors.New(...)`; typed errors when the
caller needs data from them.
- **`panic` only for genuinely unrecoverable programmer error**, never for expected runtime
conditions. A library never panics on input it was designed to receive.
- `defer` the cleanup immediately after acquiring the resource, always.
---
## 5. Context
- **Every function that does I/O takes `ctx context.Context` as its first parameter.**
- Never store a context in a struct field.
- Never pass `nil` — `context.TODO()` marks a genuine gap.
- Honor cancellation: select on `ctx.Done()` in loops, and pass ctx down to every HTTP call
(`noctx` enforces this).
- Set timeouts on outbound requests. A default `http.Client` has no timeout at all.
---
## 6. Dependencies — Official SDKs First
Follow the global reuse-first ordering. In Go, strongly prefer **official/first-party SDKs**
over third-party wrappers — they track API changes and handle auth and pagination correctly.
Choose **pure-Go implementations over CGO ones** whenever the trade is acceptable: a CGO
dependency turns a static single-binary build into a toolchain and glibc problem, and rules
out a `FROM scratch`/Alpine container image.
```bash
CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o bin/app ./cmd/app
```
`go mod tidy` before committing, and commit `go.sum`.
---
## 7. Concurrency
- **Share memory by communicating.** Channels for handoff; `sync.Mutex` for guarding a struct's
own fields. Don't mix both for the same state.
- **`go test -race` is not optional** — it belongs in the gate, not in an occasional manual run.
- Every goroutine has a defined exit path. `errgroup.Group` for a set of related goroutines so
errors propagate and cancellation fans out.
- **Events over polling** — subscribe to a watch/notify API rather than looping on a ticker.
Where a ticker is genuinely necessary, make the interval configurable and document why.
- Never leak a goroutine on a channel nobody reads.
---
## 8. Structure
- `cmd/<binary>/` for entry points; `pkg/` or `internal/` for the actual code.
- `internal/` for anything that must not become public API — it is compiler-enforced.
- One package per concern; **package names are lowercase, single-word, no underscores**, and
never `util` or `common`.
- Keep the exported surface small. Unexported by default.
- Embed frontend/static assets with `//go:embed` for single-binary distribution.
- File and function size limits from the general rules apply.
---
## 9. Testing
- Table-driven tests are the default idiom.
- `t.Parallel()` where tests are genuinely independent.
- `t.TempDir()` for filesystem work — it cleans itself up.
- `httptest` for HTTP handlers and for faking upstreams; do not hit real services in unit tests.
- **Regression test every fixed bug.**
- **Never run destructive operations against real accounts or user data during validation** —
temporary fixtures only.
---
## 10. Related
- [`coding-dev.md`](skills-public/coding-dev.md) — the general rules this extends.
- [`js-typescript.md`](skills-public/code-languages/js-typescript.md) — for the embedded frontend in a Go service.