
About
Apply Go best practices for type-safe, idiomatic code with proper error handling, concurrency, and testing patterns.
name: go-ops description: "Go development patterns, concurrency, error handling, testing, and project structure. Use for: golang, go, goroutine, channel, context, errgroup, go test, go mod, go build, interface, generics, table-driven tests, worker pool, sync.Mutex, sync.WaitGroup, pprof, go vet, golangci-lint, go workspace, functional options, middleware, http handler." license: MIT allowed-tools: "Read Write Bash" metadata: author: claude-mods related-skills: docker-ops, ci-cd-ops, api-design-ops, testing-ops
Go Operations
Comprehensive Go skill covering idiomatic patterns, concurrency, and production practices.
Module Quick Start
# New module
go mod init github.com/user/project
# Add dependency
go get github.com/lib/pq@latest
# Tidy (remove unused, add missing)
go mod tidy
# Vendor dependencies
go mod vendor
# Workspace (multi-module)
go work init ./api ./shared
go work use ./cli
Error Handling Decision Tree
What kind of error?
│
├─ Known, expected condition (e.g. "not found")
│ └─ Sentinel error: var ErrNotFound = errors.New("not found")
│ └─ Caller checks: errors.Is(err, ErrNotFound)
│
├─ Need to carry structured data (status code, field name)
│ └─ Custom error type: type ValidationError struct { Field, Message string }
│ └─ Implement Error() string
│ └─ Caller checks: errors.As(err, &validErr)
│
├─ Adding context to an existing error
│ └─ Wrap: fmt.Errorf("load config: %w", err)
│ └─ Preserves original for Is/As checks
│
├─ Truly unrecoverable (corrupted state, programmer bug)
│ └─ panic("invariant violated: ...")
│ └─ Almost never in library code
│
└─ Multiple errors from concurrent work
└─ errors.Join(err1, err2) or multierr package
Error Wrapping Convention
// Add context at each layer, don't repeat the function name
func LoadUser(id int) (*User, error) {
row, err := db.Query("SELECT ...", id)
if err != nil {
return nil, fmt.Errorf("load user %d: %w", id, err)
}
// ...
}
Concurrency Decision Tree
What's the concurrency pattern?
│
├─ Run N independent tasks, collect results
│ └─ errgroup.Group (cancels on first error)
│
├─ Fire-and-forget background work
│ └─ go func() with context for cancellation
│ └─ ALWAYS handle the error or log it
│
├─ Producer/consumer pipeline
│ └─ Channels (buffered for throughput)
│ └─ Close channel when producer is done
│
├─ Rate-limited concurrent work
│ └─ Semaphore: make(chan struct{}, maxConcurrency)
│
├─ Shared mutable state
│ └─ sync.Mutex or sync.RWMutex
│ └─ Prefer channels if the state is simple
│
├─ One-time initialization
│ └─ sync.Once
│
└─ Wait for N goroutines to finish (no error collection)
└─ sync.WaitGroup
errgroup Quick Start
import "golang.org/x/sync/errgroup"
g, ctx := errgroup.WithContext(ctx)
g.SetLimit(10) // max 10 concurrent goroutines
for _, url := range urls {
g.Go(func() error {
return fetch(ctx, url)
})
}
if err := g.Wait(); err != nil {
return fmt.Errorf("fetch urls: %w", err)
}
Deep dive: Load ./references/concurrency.md for worker pools, fan-out/fan-in, pipeline patterns, context best practices.
Interface Design
Accept interfaces, return structs.
// Good: function accepts interface
func Process(r io.Reader) error { ... }
// Good: return concrete type
func NewServer(cfg Config) *Server { ... }
// Bad: returning interface (hides implementation, prevents extension)
func NewServer(cfg Config) ServerInterface { ... }
Common Stdlib Interfaces
| Interface | Methods | Use For |
|-----------|---------|---------|
| io.Reader | Read([]byte) (int, error) | Any byte source |
| io.Writer | Write([]byte) (int, error) | Any byte sink |
| io.Closer | Close() error | Resource cleanup |
| fmt.Stringer | String() string | String representation |
| error | Error() string | Error values |
| sort.Interface | Len, Less, Swap | Custom sorting |
| http.Handler | ServeHTTP(w, r) | HTTP handlers |
| encoding.BinaryMarshaler | MarshalBinary() ([]byte, error) | Binary encoding |
Functional Options Pattern
type Option func(*Server)
func WithPort(port int) Option {
return func(s *Server) { s.port = port }
}
func WithTimeout(d time.Duration) Option {
return func(s *Server) { s.timeout = d }
}
func NewServer(opts ...Option) *Server {
s := &Server{port: 8080, timeout: 30 * time.Second} // defaults
for _, opt := range opts {
opt(s)
}
return s
}
// Usage
srv := NewServer(WithPort(9090), WithTimeout(5*time.Second))
Deep dive: Load ./references/interfaces-generics.md for generics, type constraints, embedding, type assertions.
Testing Quick Reference
// Table-driven test
func TestAdd(t *testing.T) {
tests := []struct {
name string
a, b int
expected int
}{
{"positive", 1, 2, 3},
{"zero", 0, 0, 0},

