Claude
Skills
Sign in
Back

go-pro

Included with Lifetime
$97 forever

Idiomatic Go with proper error handling, concurrency patterns, slog, generics, and clean package design. Use when implementing, debugging, refactoring, or reviewing Go code; designing package boundaries; resolving race conditions or goroutine leaks; setting up table-driven tests; choosing between channels and mutexes; or tuning module layout. Applies to any Go work unless a more specific role overrides.

Design

What this skill does


# Go Pro

Senior-level Go expertise for production projects. Focuses on idiomatic patterns, simplicity, and Go's design philosophy.

## When Invoked

1. Review `go.mod` and `.golangci.yml` for project conventions
2. For build system setup, invoke the **just-pro** skill
3. Apply Go idioms and established project patterns

## Core Standards

**Required:**
- All exported identifiers have doc comments
- All errors checked and handled (no `_ = err`)
- NO `panic()` for recoverable errors
- golangci-lint passes with project configuration
- Table-driven tests for multiple cases

**Foundational Principles:**
- **Single Responsibility**: One package = one purpose, one function = one job
- **No God Objects**: Split large structs; if it has 10+ fields or methods, decompose
- **Dependency Injection**: Pass dependencies, don't create them internally
- **Small Interfaces**: 1-3 methods max; compose larger behaviors from small interfaces

---

## Project Setup (Go 1.25+)

### Version Management

Pin Go version with [mise](https://mise.jdx.dev): `mise use [email protected]` (creates `.mise.toml` — commit it). Team members run `mise install`. See **mise** skill for setup.

### New Project Quick Start

```bash
# Initialize
go mod init github.com/org/project
go mod edit -go=1.25

# Add toolchain dependencies (tracked in go.mod)
go get -tool github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest
go get -tool golang.org/x/tools/cmd/goimports@latest
go get -tool golang.org/x/vuln/cmd/govulncheck@latest

# Copy configs from this skill's references/ directory:
#   references/gitignore          → .gitignore
#   references/golangci-v2.yml    → .golangci.yml
# For build system, invoke just-pro skill

# Verify
just check   # Or: go tool golangci-lint run
```

### Developer Onboarding

```bash
git clone <repo> && cd <repo>
just setup         # Runs mise trust/install + go mod download
just check         # Verify everything works
```

Or manually:
```bash
mise trust && mise install  # Get pinned Go version
go mod download             # Get dependencies
```

**Why `go get -tool`?** Tools versioned in go.mod = reproducible builds, same versions for all devs, no separate installation needed.

---

## Build System

**Invoke the `just-pro` skill** for build system setup. It covers:
- Simple repos vs monorepos
- Hierarchical justfile modules
- Go-specific templates (`references/package-go.just`)

**Why just?** Consistent toolchain frontend between agents and humans. Instead of remembering `go tool golangci-lint run --fix`, use `just fix`.

---

## Quality Assurance

**Auto-Fix First** - Always try auto-fix before manual fixes:

```bash
just fix             # Or: go tool golangci-lint run --fix && go tool goimports -w .
```

**Verification:**
```bash
just check           # Or: go tool golangci-lint run && go test -race ./... && go tool govulncheck ./...
```

---

## Quick Reference

### Error Handling

| Pattern | Use |
|---------|-----|
| `return err` | Propagate unchanged (internal errors) |
| `fmt.Errorf("context: %w", err)` | Wrap with context (cross-boundary) |
| `errors.Is(err, target)` | Check specific error |
| `errors.As(err, &target)` | Extract typed error |

**Sentinel Errors** - Define package-level errors for expected conditions:
```go
var ErrNotFound = errors.New("not found")
var ErrInvalidInput = errors.New("invalid input")
```

### Generics

```go
// Constrained generics - prefer specific constraints
func Map[T, U any](items []T, fn func(T) U) []U {
    result := make([]U, len(items))
    for i, item := range items {
        result[i] = fn(item)
    }
    return result
}

// Type constraints - use interfaces
type Ordered interface {
    ~int | ~int64 | ~float64 | ~string
}

func Max[T Ordered](a, b T) T {
    if a > b {
        return a
    }
    return b
}

// Avoid: overly generic signatures that lose type safety
// Prefer: concrete types until generics are clearly needed
```

### Structured Logging (slog)

```go
import "log/slog"

// Package-level logger with context
func NewService(logger *slog.Logger) *Service {
    return &Service{
        log: logger.With("component", "service"),
    }
}

// Structured logging with levels
s.log.Info("request processed",
    "method", r.Method,
    "path", r.URL.Path,
    "duration", time.Since(start),
)

s.log.Error("operation failed",
    "err", err,
    "user_id", userID,
)
```

### Iterators (Go 1.23+)

```go
import "iter"

// Return iterators for large collections
func (db *DB) Users() iter.Seq[User] {
    return func(yield func(User) bool) {
        rows, _ := db.Query("SELECT * FROM users")
        defer rows.Close()
        for rows.Next() {
            var u User
            rows.Scan(&u.ID, &u.Name)
            if !yield(u) {
                return
            }
        }
    }
}

// Consume with range
for user := range db.Users() {
    process(user)
}

// Seq2 for key-value pairs
func (m *Map[K, V]) All() iter.Seq2[K, V]
```

### Concurrency

| Pattern | Use |
|---------|-----|
| `sync.WaitGroup` | Wait for goroutines |
| `sync.Mutex` / `RWMutex` | Protect shared state |
| `context.Context` | Cancellation/timeouts |
| `errgroup.Group` | Concurrent with error collection |

```go
// Context-aware work
func DoWork(ctx context.Context, arg string) error {
    select {
    case <-ctx.Done():
        return ctx.Err()
    default:
    }
    // ... work
}
```

### Testing

```go
// Table-driven tests with subtests
func TestParse(t *testing.T) {
    tests := []struct {
        name    string
        input   string
        want    Result
        wantErr bool
    }{
        {name: "valid", input: "foo", want: Result{Value: "foo"}},
        {name: "empty", input: "", wantErr: true},
        {name: "special", input: "a@b", want: Result{Value: "a@b"}},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got, err := Parse(tt.input)
            if (err != nil) != tt.wantErr {
                t.Errorf("Parse() error = %v, wantErr %v", err, tt.wantErr)
                return
            }
            if got != tt.want {
                t.Errorf("Parse() = %v, want %v", got, tt.want)
            }
        })
    }
}

// Testify for complex assertions
import "github.com/stretchr/testify/assert"
import "github.com/stretchr/testify/require"

func TestService(t *testing.T) {
    require.NoError(t, err)           // Fail fast
    assert.Equal(t, expected, actual) // Continue on failure
    assert.Len(t, items, 3)
    assert.Contains(t, items, target)
}
```

### Pointer vs Value Receivers

```go
// Use pointer receivers when:
// - Method modifies the receiver
// - Receiver is large (avoid copy)
// - Consistency: if any method needs pointer, use pointer for all
func (s *Service) UpdateConfig(cfg Config) { s.cfg = cfg }

// Use value receivers when:
// - Receiver is small (int, string, small struct)
// - Method is read-only and receiver is immutable
func (p Point) Distance(other Point) float64 { ... }
```

### Package Organization

```
project/
├── cmd/appname/main.go   # Entry point
├── internal/             # Private packages
│   ├── api/              # Handlers
│   └── domain/           # Business logic
├── go.mod
├── .golangci.yml
└── justfile
```

**Rules:** One package = one purpose. Use `internal/` for implementation. Avoid `util`, `common`, `helpers` packages.

---

## DX Patterns

### Doctor Recipe with Version Validation

Doctor scripts should validate that toolchain versions meet requirements, not just check existence:

```just
# Validate toolchain versions meet requirements
doctor:
    #!/usr/bin/env bash
    set -euo pipefail
    echo "Checking toolchain..."

    # Validate Go version (requires 1.25+)
    GO_VERSION=$(go version | grep -oE 'go[0-9]+\.[0-9]+' | sed 's/go//')
    if [[ "$(printf '%s\n' "1.25" "$GO_VERSION" | sort -V | head -1)" != "1.25" ]]; then
        echo "FAIL: Go $GO_VERSION < 1.25 required"
        exit 1
    fi
    echo "✓ Go $GO_VERSION"

    # Add more version checks as

Related in Design