Skip to main content

go

Use when writing, reviewing, testing, or shipping Go code and HTTP services: idioms, `%w` error wrapping, goroutine/context/errgroup concurrency, net/http 1.22 routing, log/slog, project layout, table-driven tests, Go hardening. NOT language-agnostic threat modeling (that is `secure-coding`), NOT Dockerfile/CI shipping (that is `deployment`).

معلومات المصدر

المستودع
ericrisco/rsc-harness
آخر نشاط في المصدر
١٧ أغسطس ٢٠٢٦ في ٢٠:٠٤
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
١٤٢
التفرعات
١١

خيارات التثبيت

يُحدَّد Prompt الذي يراجع المصدر أولًا بشكل افتراضي. يمكنك التبديل إلى أمر مباشر أو تنزيل نسخة محلية.

مراجعة ملفات المصدر

اقرأ SKILL.md وأي ملفات مرافقة يعرضها SkillsMP قبل أن تقرر التثبيت.

مستكشف الملفات
7 ملفات

عرض SKILL.md

SKILL.md
تعليمات المصدر · معاينة للقراءة فقط
name
go
description
Use when writing, reviewing, testing, or shipping Go code and HTTP services: idioms, `%w` error wrapping, goroutine/context/errgroup concurrency, net/http 1.22 routing, log/slog, project layout, table-driven tests, Go hardening. NOT language-agnostic threat modeling (that is `secure-coding`), NOT Dockerfile/CI shipping (that is `deployment`).
tags
["go","golang","http","backend","service"]
recommends
["postgresdb","secure-coding","deployment"]
origin
risco
# Idiomatic Go services Targets **Go 1.22+** (Go 1.26 is the current stable release): enhanced `net/http` routing (`mux.HandleFunc("GET /users/{id}", h)` + `r.PathValue`), `log/slog` structured logging, and fixed loop-variable semantics (no more `tt := tt`). > **⚠️ SDD new-feature gate — read this first.** If this skill fired on a **new, non-trivial feature or behaviour change** and there is **no approved spec + plan** under `02-DOCS/wiki/sdd/`, STOP — do **not** write feature code yet. Hand off to `../specify/SKILL.md` first: it runs brainstorm → spec → plan → tasks before any code, then routes back here once the plan is approved. Build here directly only for a genuinely one-line / low-risk change. Method: `../sdd/SKILL.md`. ## Boundary Go error handling and HTTP contract design (status-code taxonomy, REST resource naming) live **here** — this skill is the canonical authority for both in Go. Delegate outward: - Language-agnostic abuse/authz review, threat modeling, OWASP-class bugs -> [`secure-coding`](../secure-coding/SKILL.md). This skill keeps the Go-specific controls: SQL params, server timeouts, `govulncheck`, TLS defaults. - Containerfile / k8s / CI pipeline authoring -> [`deployment`](../deployment/SKILL.md). This skill ships only a Docker note + `ldflags`. - Recording per-project conventions in a workspace wiki -> [`harness`](../harness/SKILL.md) (see "Project grounding" below). Non-service Go (CLI tooling, codegen, ML): the patterns apply, but the HTTP/production half is irrelevant. ## Idioms **Useful zero value.** Design types so the zero value works before any constructor. ```go // Good: zero-value Counter is ready; the zero-value mutex is unlocked. var b bytes.Buffer too. type Counter struct { mu sync.Mutex n int } func (c *Counter) Inc() { c.mu.Lock(); c.n++; c.mu.Unlock() } // Bad: nil map field panics on first write (assignment to entry in nil map); hidden init step. type Registry struct{ items map[string]int } func (r *Registry) Add(k string) { r.items[k]++ } // panic if items was never make()'d ``` **Accept interfaces, return structs.** Return the concrete type; declare the interface where it is consumed. ```go type UserStore interface { // declared in package service - only what it needs GetUser(ctx context.Context, id string) (*User, error) } func NewService(s UserStore) *Service { return &Service{store: s} } // Good: return *Service // Bad: func NewService(s UserStore) UserStore - returning the interface hides the type. ``` **Functional options.** Defaults first, then apply options. ```go type Server struct { addr string timeout time.Duration logger *slog.Logger } type Option func(*Server) func WithTimeout(d time.Duration) Option { return func(s *Server) { s.timeout = d } } func WithLogger(l *slog.Logger) Option { return func(s *Server) { s.logger = l } } func NewServer(addr string, opts ...Option) *Server { s := &Server{addr: addr, timeout: 30 * time.Second, logger: slog.Default()} // defaults first for _, opt := range opts { opt(s) } return s } ``` Use a plain `Config` struct once options exceed ~5; options are for optional, composable tuning, not required fields. **Embedding for composition.** Embed to borrow a behavior, not to fake inheritance. ```go // Good: the service gets .Info/.Error for free from the embedded logger. type Service struct { *slog.Logger store UserStore } // Bad: deep type trees (Base -> Middle -> Leaf) modeling "is-a" inheritance - avoid. ``` **Early return.** Clear over clever: invert the error and `return`; keep the happy path flat (no arrow code). ```go // Good: each failure returns immediately; the success path is unindented. func save(ctx context.Context, u *User) error { if u == nil { return errors.New("nil user") } if err := validate(u); err != nil { return fmt.Errorf("validate: %w", err) } return store.Put(ctx, u) } // Bad: if u != nil { if err := validate(u); err == nil { ... } else { ... } } - arrow code. ``` **No package-level mutable state.** Inject via constructor (`func New(db *sql.DB) *Server`), never a global `var db *sql.DB` opened in `init()` - globals couple everything and kill testability. **Receivers.** Pick value or pointer per type and stay consistent across its method set; mutating / large / contains-`sync` -> pointer. **Go 1.22 loopvar.** Loop variables are per-iteration now. Stop emitting the workaround: inside `for _, tt := range tests` the line `// tt := tt` is obsolete - DELETE it. ## Errors **Sentinel vs typed.** Sentinels for identity; typed errors for data. ```go var ErrNotFound = errors.New("not found") // sentinel: identity type ValidationError struct{ Field, Msg string } // typed: carries data func (e *ValidationError) Error() string { return fmt.Sprintf("%s: %s", e.Field, e.Msg) } ``` **Wrap and classify.** Wrap every crossed boundary with `%w`; never compare message strings. ```go err := fmt.Errorf("find user %s: %w", id, ErrNotFound) if errors.Is(err, ErrNotFound) { /* sentinel match through the wrap chain */ } var verr *ValidationError if errors.As(err, &verr) { /* typed match: verr.Field, verr.Msg */ } joined := errors.Join(err1, err2) // 1.20+: aggregate; Is/As traverse both ``` **3-layer boundary (the canonical flow).** Repo wraps the driver sentinel into a domain sentinel; service passes it through; handler classifies once and maps to a status, logging only the unexpected. ```go // repository: translate sql.ErrNoRows into a domain sentinel, keep the chain. func (r *Repo) GetUser(ctx context.Context, id string) (*User, error) { var u User err := r.db.QueryRowContext(ctx, "SELECT id, name FROM users WHERE id = $1", id). Scan(&u.ID, &u.Name) if errors.Is(err, sql.ErrNoRows) { return nil, fmt.Errorf("user %s: %w", id, ErrNotFound) } if err != nil { return nil, fmt.Errorf("query user %s: %w", id, err) } return &u, nil } // handler: classify once, map to HTTP status. func (h *Handler) getUser(w http.ResponseWriter, r *http.Request) { u, err := h.svc.GetUser(r.Context(), r.PathValue("id")) switch { case err == nil: writeJSON(w, http.StatusOK, u) case errors.Is(err, ErrNotFound): http.Error(w, "not found", http.StatusNotFound) default: slog.Error("get user", "err", err) http.Error(w, "internal error", http.StatusInternalServerError) } } ``` Full handler adapter (error-returning `apiHandler`) -> `references/http-services.md`. **`defer` + named return** to capture `Close()` errors: ```go func read(name string) (err error) { f, e := os.Open(name) if e != nil { return e } defer func() { err = errors.Join(err, f.Close()) }() // capture Close() into the return return nil } ``` ## Concurrency (essentials) `context.Context` is the **first** param of every call, never stored in a struct, never `nil` (use `context.TODO()` while wiring). Bound work with a context deadline; bound concurrency with `errgroup` — the derived `ctx` cancels siblings on first error, and `g.SetLimit(n)` caps in-flight goroutines: ```go g, ctx := errgroup.WithContext(ctx) g.SetLimit(8) for _, id := range ids { g.Go(func() error { return process(ctx, id) }) // Go 1.22+: no id := id needed } err := g.Wait() ``` Three rules cover most service code: every goroutine needs a known exit path (a started goroutine you cannot stop is a leak); an unbuffered `ch <- v` with no receiver after a cancel blocks forever, so buffer it and `select` on `ctx.Done()`; **run `-race` in CI**. Low-level needs map to `sync.Once` (lazy init), `sync.RWMutex` (read-heavy state), `sync/atomic` (`atomic.Int64` counters). Full implementations — context plumbing, channel/select patterns, leak detection, worker pools, pipelines, fan-in/out, semaphores, `singleflight`, and a `withRetry` helper (backoff + full jitter, `ctx`-aware, **never retries 4xx**) -> `references/concurrency.md`. ## HTTP services (essentials) Go 1.22 routed mux — method and path live in the pattern; the `error`-returning adapter centralizes status mapping: ```go mux := http.NewServeMux() mux.HandleFunc("GET /users/{id}", getUser) // 405 on wrong method, 404 on no match id := r.PathValue("id") // inside the handler type apiHandler func(http.ResponseWriter, *http.Request) error func (h apiHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if err := h(w, r); err != nil { /* classify via errors.Is/As -> status + slog */ } } ``` Set **all four** `http.Server` timeouts (`ReadHeaderTimeout`, `ReadTimeout`, `WriteTimeout`, `IdleTimeout`) — an unbounded read is a Slowloris DoS. Graceful shutdown on signal: `signal.NotifyContext(ctx, os.Interrupt, syscall.SIGTERM)`, then `srv.Shutdown(shutdownCtx)` on `<-ctx.Done()`. Routing patterns, chi vs stdlib, the full middleware chain (request-id, slog, panic-recovery, timeout), config, timeout values, functional-options server, and JSON helpers -> `references/http-services.md`. ## Project layout ```text cmd/api/main.go # entrypoint: wiring only internal/handler/ # HTTP adapters internal/service/ # business logic; defines the interfaces it needs internal/repository/ # data access (pgx); implements service interfaces internal/config/ # env parsing, validation pkg/ # ONLY genuinely reusable, stable public API testdata/ # fixtures, golden files go.mod go.sum ``` Wire the layers with **constructor injection**, outermost depends inward: `repo := repository.New(db); svc := service.New(repo); h := handler.New(svc)`. Package naming: short, lowercase, no underscores, no `util`/`common`, avoid stutter (`user.User`, not `user.UserStruct`). Interfaces live on the **consumer side**: the `service` package declares `UserStore`; the `repository` package implements it without importing the interface. ## Testing (essentials) Table-driven with subtests and parallelism: ```go func TestParse(t *testing.T) { tests := []struct { name string in string wantErr error }{ {"ok", "42", nil}, {"bad", "x", ErrInvalid}, } for _, tt := range tests { // Go 1.22+: no tt := tt needed t.Run(tt.name, func(t *testing.T) { t.Parallel() _, err := Parse(tt.in) if !errors.Is(err, tt.wantErr) { // classify, not just != nil t.Fatalf("got %v, want %v", err, tt.wantErr) } }) } } ``` HTTP handlers via `httptest`: `req := httptest.NewRequest("GET", "/users/1", nil)`; `w := httptest.NewRecorder()`; `h.ServeHTTP(w, req)`; then assert on `w.Code` / `w.Body`. Use `t.Helper()` in assertions, `t.TempDir()` for files, `t.Cleanup()` for teardown, `t.Setenv()` for env. Run `go test -race -cover ./...`, and treat `go vet` / `staticcheck` failures as build failures. Stdlib `testing` is the default; reach for `testify/require` only for deep-equality or large suites. Golden files, fuzzing, benchmarks, httptest matrices, interface fakes -> `references/testing.md`. ## Security (embedded) Validate at the boundary: parametrize SQL (PostgreSQL; prefer `pgx v5` over `database/sql`); cap request bodies and reject unknown fields; set a TLS floor and trust the `crypto/tls` defaults: ```go // Good // Bad: string interpolation = SQL injection. db.QueryContext(ctx, "... WHERE id = $1", id) // db.QueryContext(ctx, fmt.Sprintf("... '%s'", id)) r.Body = http.MaxBytesReader(w, r.Body, 1<<20) // 1 MiB cap dec := json.NewDecoder(r.Body) dec.DisallowUnknownFields() tlsCfg := &tls.Config{MinVersion: tls.VersionTLS12} // do not hand-pick cipher suites ``` **Server timeouts** are a DoS control - set all four (see HTTP services above). Run `govulncheck ./...` in CI; keep deps honest with `go mod tidy` + `go mod verify`. Read secrets from env / a secret manager, never log them; redact tokens with slog `ReplaceAttr`. Deeper authz/abuse review -> `secure-coding`. ## Production
عرض على GitHub
ملف SKILL.md هذا كبير جدا، لذلك يعرض SkillsMP القسم الاول فقط هنا. عرض على GitHub