Claude Code Plugins

Community-maintained marketplace

Feedback

Modern Go patterns, idioms, and best practices from Go 1.18+. Use when user needs guidance on idiomatic Go code, design patterns, or modern Go features like generics and workspaces.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name go-patterns
description Modern Go patterns, idioms, and best practices from Go 1.18+. Use when user needs guidance on idiomatic Go code, design patterns, or modern Go features like generics and workspaces.

Go Patterns Skill

This skill provides comprehensive guidance on modern Go patterns, idioms, and best practices, with special focus on features introduced in Go 1.18 and later.

When to Use

Activate this skill when:

  • Writing idiomatic Go code
  • Implementing design patterns in Go
  • Using modern Go features (generics, fuzzing, workspaces)
  • Refactoring code to be more idiomatic
  • Teaching Go best practices
  • Code review for idiom compliance

Modern Go Features

Generics (Go 1.18+)

Type Parameters:

// Generic function
func Map[T, U any](slice []T, f func(T) U) []U {
    result := make([]U, len(slice))
    for i, v := range slice {
        result[i] = f(v)
    }
    return result
}

// Usage
numbers := []int{1, 2, 3, 4, 5}
doubled := Map(numbers, func(n int) int { return n * 2 })

Type Constraints:

// Ordered constraint
type Ordered interface {
    ~int | ~int8 | ~int16 | ~int32 | ~int64 |
    ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 |
    ~float32 | ~float64 | ~string
}

func Min[T Ordered](a, b T) T {
    if a < b {
        return a
    }
    return b
}

// Custom constraints
type Numeric interface {
    ~int | ~int64 | ~float64
}

func Sum[T Numeric](values []T) T {
    var sum T
    for _, v := range values {
        sum += v
    }
    return sum
}

Generic Data Structures:

// Generic stack
type Stack[T any] struct {
    items []T
}

func NewStack[T any]() *Stack[T] {
    return &Stack[T]{items: make([]T, 0)}
}

func (s *Stack[T]) Push(item T) {
    s.items = append(s.items, item)
}

func (s *Stack[T]) Pop() (T, bool) {
    if len(s.items) == 0 {
        var zero T
        return zero, false
    }
    item := s.items[len(s.items)-1]
    s.items = s.items[:len(s.items)-1]
    return item, true
}

// Generic map utilities
func Keys[K comparable, V any](m map[K]V) []K {
    keys := make([]K, 0, len(m))
    for k := range m {
        keys = append(keys, k)
    }
    return keys
}

func Values[K comparable, V any](m map[K]V) []V {
    values := make([]V, 0, len(m))
    for _, v := range m {
        values = append(values, v)
    }
    return values
}

Workspaces (Go 1.18+)

go.work file:

go 1.21

use (
    ./service
    ./shared
    ./tools
)

replace example.com/legacy => ./vendor/legacy

Benefits:

  • Multi-module development
  • Local dependency overrides
  • Simplified testing across modules
  • Better monorepo support

Essential Go Patterns

Functional Options Pattern

type Server struct {
    host    string
    port    int
    timeout time.Duration
    logger  *log.Logger
}

type Option func(*Server)

func WithHost(host string) Option {
    return func(s *Server) {
        s.host = host
    }
}

func WithPort(port int) Option {
    return func(s *Server) {
        s.port = port
    }
}

func WithTimeout(timeout time.Duration) Option {
    return func(s *Server) {
        s.timeout = timeout
    }
}

func WithLogger(logger *log.Logger) Option {
    return func(s *Server) {
        s.logger = logger
    }
}

func NewServer(opts ...Option) *Server {
    s := &Server{
        host:    "localhost",
        port:    8080,
        timeout: 30 * time.Second,
        logger:  log.Default(),
    }

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

    return s
}

// Usage
server := NewServer(
    WithHost("0.0.0.0"),
    WithPort(3000),
    WithTimeout(60 * time.Second),
)

Builder Pattern

type Query struct {
    table      string
    where      []string
    orderBy    string
    limit      int
    offset     int
}

type QueryBuilder struct {
    query Query
}

func NewQueryBuilder(table string) *QueryBuilder {
    return &QueryBuilder{
        query: Query{table: table},
    }
}

func (b *QueryBuilder) Where(condition string) *QueryBuilder {
    b.query.where = append(b.query.where, condition)
    return b
}

func (b *QueryBuilder) OrderBy(field string) *QueryBuilder {
    b.query.orderBy = field
    return b
}

func (b *QueryBuilder) Limit(limit int) *QueryBuilder {
    b.query.limit = limit
    return b
}

func (b *QueryBuilder) Offset(offset int) *QueryBuilder {
    b.query.offset = offset
    return b
}

func (b *QueryBuilder) Build() Query {
    return b.query
}

// Usage
query := NewQueryBuilder("users").
    Where("age > 18").
    Where("active = true").
    OrderBy("created_at DESC").
    Limit(10).
    Offset(20).
    Build()

Strategy Pattern

// Strategy interface
type PaymentStrategy interface {
    Pay(amount float64) error
}

// Concrete strategies
type CreditCardPayment struct {
    cardNumber string
}

func (c *CreditCardPayment) Pay(amount float64) error {
    fmt.Printf("Paying $%.2f with credit card %s\n", amount, c.cardNumber)
    return nil
}

type PayPalPayment struct {
    email string
}

func (p *PayPalPayment) Pay(amount float64) error {
    fmt.Printf("Paying $%.2f with PayPal account %s\n", amount, p.email)
    return nil
}

type CryptoPayment struct {
    walletAddress string
}

func (c *CryptoPayment) Pay(amount float64) error {
    fmt.Printf("Paying $%.2f to wallet %s\n", amount, c.walletAddress)
    return nil
}

// Context
type PaymentProcessor struct {
    strategy PaymentStrategy
}

func NewPaymentProcessor(strategy PaymentStrategy) *PaymentProcessor {
    return &PaymentProcessor{strategy: strategy}
}

func (p *PaymentProcessor) ProcessPayment(amount float64) error {
    return p.strategy.Pay(amount)
}

// Usage
processor := NewPaymentProcessor(&CreditCardPayment{cardNumber: "1234-5678"})
processor.ProcessPayment(100.00)

processor = NewPaymentProcessor(&PayPalPayment{email: "user@example.com"})
processor.ProcessPayment(50.00)

Observer Pattern

type Observer interface {
    Update(event Event)
}

type Event struct {
    Type string
    Data interface{}
}

type Subject struct {
    observers []Observer
}

func (s *Subject) Attach(observer Observer) {
    s.observers = append(s.observers, observer)
}

func (s *Subject) Detach(observer Observer) {
    for i, obs := range s.observers {
        if obs == observer {
            s.observers = append(s.observers[:i], s.observers[i+1:]...)
            break
        }
    }
}

func (s *Subject) Notify(event Event) {
    for _, observer := range s.observers {
        observer.Update(event)
    }
}

// Concrete observer
type Logger struct {
    name string
}

func (l *Logger) Update(event Event) {
    fmt.Printf("[%s] Received event: %s\n", l.name, event.Type)
}

// Usage
subject := &Subject{}
logger1 := &Logger{name: "Logger1"}
logger2 := &Logger{name: "Logger2"}

subject.Attach(logger1)
subject.Attach(logger2)

subject.Notify(Event{Type: "UserCreated", Data: "user123"})

Idiomatic Go Patterns

Error Handling

Sentinel Errors:

var (
    ErrNotFound     = errors.New("resource not found")
    ErrUnauthorized = errors.New("unauthorized access")
    ErrInvalidInput = errors.New("invalid input")
)

func GetUser(id string) (*User, error) {
    if id == "" {
        return nil, ErrInvalidInput
    }

    user := findUser(id)
    if user == nil {
        return nil, ErrNotFound
    }

    return user, nil
}

// Check with errors.Is
if errors.Is(err, ErrNotFound) {
    // Handle not found
}

Custom Error Types:

type ValidationError struct {
    Field   string
    Message string
}

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

// Check with errors.As
var valErr *ValidationError
if errors.As(err, &valErr) {
    fmt.Printf("Validation failed: %s\n", valErr.Field)
}

Error Wrapping:

func ProcessUser(id string) error {
    user, err := GetUser(id)
    if err != nil {
        return fmt.Errorf("process user: %w", err)
    }

    if err := ValidateUser(user); err != nil {
        return fmt.Errorf("validate user %s: %w", id, err)
    }

    return nil
}

Interface Patterns

Small Interfaces:

// Good: Small, focused interfaces
type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

type Closer interface {
    Close() error
}

// Compose interfaces
type ReadWriteCloser interface {
    Reader
    Writer
    Closer
}

Interface Segregation:

// Instead of one large interface
type Repository interface {
    Create(ctx context.Context, user *User) error
    Read(ctx context.Context, id string) (*User, error)
    Update(ctx context.Context, user *User) error
    Delete(ctx context.Context, id string) error
    List(ctx context.Context) ([]*User, error)
    Search(ctx context.Context, query string) ([]*User, error)
}

// Better: Separate interfaces
type UserCreator interface {
    Create(ctx context.Context, user *User) error
}

type UserReader interface {
    Read(ctx context.Context, id string) (*User, error)
    List(ctx context.Context) ([]*User, error)
}

type UserUpdater interface {
    Update(ctx context.Context, user *User) error
}

type UserDeleter interface {
    Delete(ctx context.Context, id string) error
}

type UserSearcher interface {
    Search(ctx context.Context, query string) ([]*User, error)
}

Context Patterns

Proper Context Usage:

func FetchData(ctx context.Context, url string) ([]byte, error) {
    // Create request with context
    req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
    if err != nil {
        return nil, fmt.Errorf("create request: %w", err)
    }

    // Check for cancellation before expensive operation
    select {
    case <-ctx.Done():
        return nil, ctx.Err()
    default:
    }

    // Execute request
    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return nil, fmt.Errorf("execute request: %w", err)
    }
    defer resp.Body.Close()

    return io.ReadAll(resp.Body)
}

// Context with timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

data, err := FetchData(ctx, "https://api.example.com/data")

Context Values:

type contextKey string

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

func WithRequestID(ctx context.Context, requestID string) context.Context {
    return context.WithValue(ctx, requestIDKey, requestID)
}

func GetRequestID(ctx context.Context) (string, bool) {
    requestID, ok := ctx.Value(requestIDKey).(string)
    return requestID, ok
}

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

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

Best Practices

  1. Accept interfaces, return structs
  2. Make the zero value useful
  3. Use composition over inheritance
  4. Handle errors explicitly
  5. Use defer for cleanup
  6. Prefer sync.RWMutex for read-heavy workloads
  7. Use context for cancellation and timeouts
  8. Keep interfaces small
  9. Document exported identifiers
  10. Use go fmt and go vet

Resources

Additional patterns and examples are available in the assets/ directory:

  • examples/ - Complete code examples
  • patterns/ - Design pattern implementations
  • antipatterns/ - Common mistakes to avoid

See references/ directory for:

  • Links to official Go documentation
  • Effective Go guidelines
  • Go proverbs
  • Community best practices