Claude Code Plugins

Community-maintained marketplace

Feedback

Go Deduplication Patterns

@dkoosis/cc-plugins
0
0

This skill should be used when the user asks about "code duplication", "jscpd", "DRY principle", "duplicate code", "clone detection", "code consolidation", or needs guidance on when and how to consolidate duplicate Go code.

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 Deduplication Patterns
description This skill should be used when the user asks about "code duplication", "jscpd", "DRY principle", "duplicate code", "clone detection", "code consolidation", or needs guidance on when and how to consolidate duplicate Go code.

Go Deduplication Patterns

Strategies for identifying and consolidating duplicate code in Go codebases.

Overview

jscpd detects copy-pasted code blocks. Not all duplication is bad - the goal is intentional, documented decisions about when to share vs duplicate.

When to Consolidate

Consolidate When:

  • Same operation with different inputs
  • Types share same behavior (interface candidate)
  • Changes would need to happen in multiple places
  • 20 lines duplicated

  • Code is complex enough that bugs could diverge

Keep Duplicated When:

  • Different domains that will evolve independently
  • <10 lines of simple code
  • Test fixtures (duplication aids readability)
  • Consolidation would create artificial coupling
  • Code is likely to diverge in the future

Pattern 1: Extract Helper Function

Use when: Identical code blocks with different inputs.

Before

// handlers/user.go
func CreateUser(w http.ResponseWriter, r *http.Request) {
    var req UserRequest
    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        http.Error(w, "invalid JSON", http.StatusBadRequest)
        return
    }
    if err := validate(req); err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    // ... user-specific logic
}

// handlers/order.go
func CreateOrder(w http.ResponseWriter, r *http.Request) {
    var req OrderRequest
    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        http.Error(w, "invalid JSON", http.StatusBadRequest)
        return
    }
    if err := validate(req); err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    // ... order-specific logic
}

After

// handlers/helpers.go
func decodeAndValidate[T any](r *http.Request, validate func(T) error) (T, error) {
    var req T
    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        return req, fmt.Errorf("invalid JSON: %w", err)
    }
    if err := validate(req); err != nil {
        return req, err
    }
    return req, nil
}

// handlers/user.go
func CreateUser(w http.ResponseWriter, r *http.Request) {
    req, err := decodeAndValidate[UserRequest](r, validateUser)
    if err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    // ... user-specific logic
}

Pattern 2: Interface Extraction

Use when: Same operations on different types.

Before

func SaveUser(db *sql.DB, u *User) error {
    query := "INSERT INTO users (name, email) VALUES (?, ?)"
    _, err := db.Exec(query, u.Name, u.Email)
    return err
}

func SaveOrder(db *sql.DB, o *Order) error {
    query := "INSERT INTO orders (product, quantity) VALUES (?, ?)"
    _, err := db.Exec(query, o.Product, o.Quantity)
    return err
}

After

type Saveable interface {
    InsertQuery() string
    InsertArgs() []any
}

func Save(db *sql.DB, item Saveable) error {
    _, err := db.Exec(item.InsertQuery(), item.InsertArgs()...)
    return err
}

func (u *User) InsertQuery() string { return "INSERT INTO users (name, email) VALUES (?, ?)" }
func (u *User) InsertArgs() []any   { return []any{u.Name, u.Email} }

Pattern 3: Generics for Type Safety

Use when: Same algorithm, different types, want compile-time safety.

Before

func FilterUsers(users []User, pred func(User) bool) []User {
    var result []User
    for _, u := range users {
        if pred(u) {
            result = append(result, u)
        }
    }
    return result
}

func FilterOrders(orders []Order, pred func(Order) bool) []Order {
    var result []Order
    for _, o := range orders {
        if pred(o) {
            result = append(result, o)
        }
    }
    return result
}

After

func Filter[T any](items []T, pred func(T) bool) []T {
    var result []T
    for _, item := range items {
        if pred(item) {
            result = append(result, item)
        }
    }
    return result
}

Pattern 4: Functional Options

Use when: Similar code with many optional variations.

Before

// Multiple functions with slight variations
func NewClientWithTimeout(url string, timeout time.Duration) *Client
func NewClientWithRetry(url string, retries int) *Client
func NewClientWithTimeoutAndRetry(url string, timeout time.Duration, retries int) *Client

After

type ClientOption func(*Client)

func WithTimeout(d time.Duration) ClientOption {
    return func(c *Client) { c.timeout = d }
}

func WithRetry(n int) ClientOption {
    return func(c *Client) { c.retries = n }
}

func NewClient(url string, opts ...ClientOption) *Client {
    c := &Client{url: url, timeout: 30 * time.Second}
    for _, opt := range opts {
        opt(c)
    }
    return c
}

Pattern 5: Documented Duplication

Use when: Consolidation would be worse than duplication.

// NOTE: This validation logic is intentionally duplicated from orders/validate.go
// User validation and order validation share structure now but will diverge
// as we add user-specific rules (e.g., email format, age verification).
// See ADR-015 for discussion.
func validateUser(u *User) error {
    if u.Name == "" {
        return errors.New("name required")
    }
    // ...
}

Placement Guidelines

Duplication Scope Placement
Within same package unexported helper in same file
Across sibling packages shared parent package
Across domains internal/common/ or domain-specific internal/shared/
Across repositories Consider shared module

Metrics

Metric Good Warning
Clone count <5 >15
Clone % <2% >5%
Largest clone <30 lines >50 lines

Verification

After consolidation:

# Tests must pass
go test ./...

# Duplication reduced
jscpd --format go ./...

# No new lint issues
golangci-lint run ./...