Files
ai-development-scaffold/.claude/skills/languages/go/SKILL.md
James Bland befb8fbaeb feat: initial Claude Code configuration scaffold
Comprehensive Claude Code guidance system with:

- 5 agents: tdd-guardian, code-reviewer, security-scanner, refactor-scan, dependency-audit
- 18 skills covering languages (Python, TypeScript, Rust, Go, Java, C#),
  infrastructure (AWS, Azure, GCP, Terraform, Ansible, Docker/K8s, Database, CI/CD),
  testing (TDD, UI, Browser), and patterns (Monorepo, API Design, Observability)
- 3 hooks: secret detection, auto-formatting, TDD git pre-commit
- Strict TDD enforcement with 80%+ coverage requirements
- Multi-model strategy: Opus for planning, Sonnet for execution (opusplan)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 15:47:34 -05:00

14 KiB

name, description
name description
go-development Go development patterns with testing, error handling, and idiomatic practices. Use when writing Go code.

Go Development Skill

Project Structure

project/
├── cmd/
│   └── server/
│       └── main.go           # Application entry point
├── internal/
│   ├── domain/               # Business logic
│   │   ├── user.go
│   │   └── user_test.go
│   ├── repository/           # Data access
│   │   ├── user_repo.go
│   │   └── user_repo_test.go
│   ├── service/              # Application services
│   │   ├── user_service.go
│   │   └── user_service_test.go
│   └── handler/              # HTTP handlers
│       ├── user_handler.go
│       └── user_handler_test.go
├── pkg/                      # Public packages
│   └── validation/
├── go.mod
├── go.sum
└── Makefile

Testing Patterns

Table-Driven Tests

package user

import (
    "testing"
)

func TestValidateEmail(t *testing.T) {
    tests := []struct {
        name    string
        email   string
        wantErr bool
    }{
        {
            name:    "valid email",
            email:   "user@example.com",
            wantErr: false,
        },
        {
            name:    "missing @ symbol",
            email:   "userexample.com",
            wantErr: true,
        },
        {
            name:    "empty email",
            email:   "",
            wantErr: true,
        },
        {
            name:    "missing domain",
            email:   "user@",
            wantErr: true,
        },
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            err := ValidateEmail(tt.email)
            if (err != nil) != tt.wantErr {
                t.Errorf("ValidateEmail(%q) error = %v, wantErr %v",
                    tt.email, err, tt.wantErr)
            }
        })
    }
}

Test Fixtures with Helper Functions

package service_test

import (
    "testing"

    "myapp/internal/domain"
)

// Test helper - creates valid user with optional overrides
func newTestUser(t *testing.T, opts ...func(*domain.User)) *domain.User {
    t.Helper()

    user := &domain.User{
        ID:    "user-123",
        Email: "test@example.com",
        Name:  "Test User",
        Role:  domain.RoleUser,
    }

    for _, opt := range opts {
        opt(user)
    }

    return user
}

// Option functions for customization
func withEmail(email string) func(*domain.User) {
    return func(u *domain.User) {
        u.Email = email
    }
}

func withRole(role domain.Role) func(*domain.User) {
    return func(u *domain.User) {
        u.Role = role
    }
}

func TestUserService_CreateUser(t *testing.T) {
    t.Run("creates user with valid data", func(t *testing.T) {
        user := newTestUser(t)

        svc := NewUserService(mockRepo)
        result, err := svc.CreateUser(user)

        if err != nil {
            t.Fatalf("unexpected error: %v", err)
        }

        if result.ID == "" {
            t.Error("expected user to have an ID")
        }
    })

    t.Run("rejects user with invalid email", func(t *testing.T) {
        user := newTestUser(t, withEmail("invalid-email"))

        svc := NewUserService(mockRepo)
        _, err := svc.CreateUser(user)

        if err == nil {
            t.Error("expected error for invalid email")
        }
    })
}

Mocking with Interfaces

package service

import (
    "context"

    "myapp/internal/domain"
)

// Repository interface for dependency injection
type UserRepository interface {
    GetByID(ctx context.Context, id string) (*domain.User, error)
    Create(ctx context.Context, user *domain.User) error
    Update(ctx context.Context, user *domain.User) error
}

// Service with injected dependencies
type UserService struct {
    repo UserRepository
}

func NewUserService(repo UserRepository) *UserService {
    return &UserService{repo: repo}
}

// Test file
package service_test

import (
    "context"
    "errors"
    "testing"

    "myapp/internal/domain"
    "myapp/internal/service"
)

// Mock implementation
type mockUserRepo struct {
    users map[string]*domain.User
    err   error
}

func newMockUserRepo() *mockUserRepo {
    return &mockUserRepo{
        users: make(map[string]*domain.User),
    }
}

func (m *mockUserRepo) GetByID(ctx context.Context, id string) (*domain.User, error) {
    if m.err != nil {
        return nil, m.err
    }
    user, ok := m.users[id]
    if !ok {
        return nil, errors.New("user not found")
    }
    return user, nil
}

func (m *mockUserRepo) Create(ctx context.Context, user *domain.User) error {
    if m.err != nil {
        return m.err
    }
    m.users[user.ID] = user
    return nil
}

func (m *mockUserRepo) Update(ctx context.Context, user *domain.User) error {
    if m.err != nil {
        return m.err
    }
    m.users[user.ID] = user
    return nil
}

Error Handling

Custom Error Types

package domain

import (
    "errors"
    "fmt"
)

// Sentinel errors for comparison
var (
    ErrNotFound      = errors.New("not found")
    ErrUnauthorized  = errors.New("unauthorized")
    ErrInvalidInput  = errors.New("invalid input")
)

// Structured error with context
type ValidationError struct {
    Field   string
    Message string
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("validation error on %s: %s", e.Field, e.Message)
}

// Error wrapping
func GetUser(ctx context.Context, id string) (*User, error) {
    user, err := repo.GetByID(ctx, id)
    if err != nil {
        return nil, fmt.Errorf("getting user %s: %w", id, err)
    }
    return user, nil
}

// Error checking
func handleError(err error) {
    if errors.Is(err, ErrNotFound) {
        // Handle not found
    }

    var validationErr *ValidationError
    if errors.As(err, &validationErr) {
        // Handle validation error
        fmt.Printf("Field %s: %s\n", validationErr.Field, validationErr.Message)
    }
}

Result Pattern (Optional)

package result

// Result type for explicit error handling
type Result[T any] struct {
    value T
    err   error
}

func Ok[T any](value T) Result[T] {
    return Result[T]{value: value}
}

func Err[T any](err error) Result[T] {
    return Result[T]{err: err}
}

func (r Result[T]) IsOk() bool {
    return r.err == nil
}

func (r Result[T]) IsErr() bool {
    return r.err != nil
}

func (r Result[T]) Unwrap() (T, error) {
    return r.value, r.err
}

func (r Result[T]) UnwrapOr(defaultValue T) T {
    if r.err != nil {
        return defaultValue
    }
    return r.value
}

// Usage
func ProcessPayment(amount float64) Result[*Receipt] {
    if amount <= 0 {
        return Err[*Receipt](ErrInvalidInput)
    }

    receipt := &Receipt{Amount: amount}
    return Ok(receipt)
}

HTTP Handlers

Chi Router Pattern

package handler

import (
    "encoding/json"
    "net/http"

    "github.com/go-chi/chi/v5"
    "github.com/go-chi/chi/v5/middleware"

    "myapp/internal/service"
)

type UserHandler struct {
    userService *service.UserService
}

func NewUserHandler(userService *service.UserService) *UserHandler {
    return &UserHandler{userService: userService}
}

func (h *UserHandler) Routes() chi.Router {
    r := chi.NewRouter()

    r.Use(middleware.Logger)
    r.Use(middleware.Recoverer)

    r.Get("/", h.ListUsers)
    r.Post("/", h.CreateUser)
    r.Get("/{userID}", h.GetUser)
    r.Put("/{userID}", h.UpdateUser)
    r.Delete("/{userID}", h.DeleteUser)

    return r
}

func (h *UserHandler) GetUser(w http.ResponseWriter, r *http.Request) {
    userID := chi.URLParam(r, "userID")

    user, err := h.userService.GetUser(r.Context(), userID)
    if err != nil {
        if errors.Is(err, domain.ErrNotFound) {
            http.Error(w, "User not found", http.StatusNotFound)
            return
        }
        http.Error(w, "Internal server error", http.StatusInternalServerError)
        return
    }

    respondJSON(w, http.StatusOK, user)
}

func (h *UserHandler) CreateUser(w http.ResponseWriter, r *http.Request) {
    var req CreateUserRequest
    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        http.Error(w, "Invalid request body", http.StatusBadRequest)
        return
    }

    if err := req.Validate(); err != nil {
        respondJSON(w, http.StatusBadRequest, map[string]string{
            "error": err.Error(),
        })
        return
    }

    user, err := h.userService.CreateUser(r.Context(), req.ToUser())
    if err != nil {
        http.Error(w, "Failed to create user", http.StatusInternalServerError)
        return
    }

    respondJSON(w, http.StatusCreated, user)
}

func respondJSON(w http.ResponseWriter, status int, data any) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(status)
    json.NewEncoder(w).Encode(data)
}

Request Validation

package handler

import (
    "regexp"
)

var emailRegex = regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`)

type CreateUserRequest struct {
    Email string `json:"email"`
    Name  string `json:"name"`
}

func (r *CreateUserRequest) Validate() error {
    if r.Email == "" {
        return &domain.ValidationError{
            Field:   "email",
            Message: "email is required",
        }
    }

    if !emailRegex.MatchString(r.Email) {
        return &domain.ValidationError{
            Field:   "email",
            Message: "invalid email format",
        }
    }

    if r.Name == "" {
        return &domain.ValidationError{
            Field:   "name",
            Message: "name is required",
        }
    }

    if len(r.Name) > 100 {
        return &domain.ValidationError{
            Field:   "name",
            Message: "name must be 100 characters or less",
        }
    }

    return nil
}

func (r *CreateUserRequest) ToUser() *domain.User {
    return &domain.User{
        Email: r.Email,
        Name:  r.Name,
    }
}

Context Usage

package service

import (
    "context"
    "time"
)

// Context with timeout
func (s *UserService) ProcessOrder(ctx context.Context, orderID string) error {
    // Create a timeout context
    ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
    defer cancel()

    // Check context before expensive operations
    select {
    case <-ctx.Done():
        return ctx.Err()
    default:
    }

    // Process order...
    return s.repo.UpdateOrder(ctx, orderID)
}

// Context values for request-scoped data
type contextKey string

const (
    userIDKey    contextKey = "userID"
    requestIDKey contextKey = "requestID"
)

func WithUserID(ctx context.Context, userID string) context.Context {
    return context.WithValue(ctx, userIDKey, userID)
}

func UserIDFromContext(ctx context.Context) (string, bool) {
    userID, ok := ctx.Value(userIDKey).(string)
    return userID, ok
}

Concurrency Patterns

Worker Pool

package worker

import (
    "context"
    "sync"
)

type Job func(ctx context.Context) error

type Pool struct {
    workers   int
    jobs      chan Job
    results   chan error
    wg        sync.WaitGroup
}

func NewPool(workers int) *Pool {
    return &Pool{
        workers: workers,
        jobs:    make(chan Job, workers*2),
        results: make(chan error, workers*2),
    }
}

func (p *Pool) Start(ctx context.Context) {
    for i := 0; i < p.workers; i++ {
        p.wg.Add(1)
        go p.worker(ctx)
    }
}

func (p *Pool) worker(ctx context.Context) {
    defer p.wg.Done()

    for {
        select {
        case <-ctx.Done():
            return
        case job, ok := <-p.jobs:
            if !ok {
                return
            }
            if err := job(ctx); err != nil {
                p.results <- err
            }
        }
    }
}

func (p *Pool) Submit(job Job) {
    p.jobs <- job
}

func (p *Pool) Close() {
    close(p.jobs)
    p.wg.Wait()
    close(p.results)
}

Error Group

package service

import (
    "context"

    "golang.org/x/sync/errgroup"
)

func (s *OrderService) ProcessBatch(ctx context.Context, orderIDs []string) error {
    g, ctx := errgroup.WithContext(ctx)

    // Limit concurrency
    g.SetLimit(10)

    for _, orderID := range orderIDs {
        orderID := orderID // Capture for goroutine

        g.Go(func() error {
            return s.ProcessOrder(ctx, orderID)
        })
    }

    return g.Wait()
}

Commands

# Testing
go test ./...                          # Run all tests
go test -v ./...                       # Verbose output
go test -race ./...                    # Race detection
go test -cover ./...                   # Coverage summary
go test -coverprofile=coverage.out ./... # Coverage file
go tool cover -html=coverage.out       # HTML coverage report

# Linting
go vet ./...                           # Built-in checks
golangci-lint run                      # Comprehensive linting

# Building
go build -o bin/server ./cmd/server   # Build binary
go build -ldflags="-s -w" -o bin/server ./cmd/server  # Smaller binary

# Dependencies
go mod tidy                            # Clean up dependencies
go mod verify                          # Verify dependencies

# Code generation
go generate ./...                      # Run generate directives

Anti-Patterns to Avoid

// ❌ BAD - Returning nil error with nil value
func GetUser(id string) (*User, error) {
    user := db.Find(id)
    return user, nil  // user might be nil!
}

// ✅ GOOD - Explicit error for not found
func GetUser(id string) (*User, error) {
    user := db.Find(id)
    if user == nil {
        return nil, ErrNotFound
    }
    return user, nil
}

// ❌ BAD - Ignoring errors
result, _ := SomeFunction()

// ✅ GOOD - Handle or propagate errors
result, err := SomeFunction()
if err != nil {
    return fmt.Errorf("calling SomeFunction: %w", err)
}

// ❌ BAD - Naked goroutine without error handling
go processItem(item)

// ✅ GOOD - Error handling with channels or error groups
g, ctx := errgroup.WithContext(ctx)
g.Go(func() error {
    return processItem(ctx, item)
})
if err := g.Wait(); err != nil {
    return err
}

// ❌ BAD - Mutex embedded in struct
type Service struct {
    sync.Mutex
    data map[string]string
}

// ✅ GOOD - Private mutex
type Service struct {
    mu   sync.Mutex
    data map[string]string
}