Files
skills/refactoring/SKILL.md
Mathias d6a71e370e
Some checks failed
release / tag (push) Has been cancelled
chore: bootstrap skills library — 19 skills + installer + CI auto-tag
Phase 1 of mathias/skills extraction (infra#62 Track D — homelab
next-step plan addendum). Imports ~/dev/.skills/ verbatim (19 skill
dirs + SKILLS_INDEX.md) and adds the installation surface:

- Taskfile.yml — install / update / list / release / check targets
- install.sh — bootstrap installer for hosts without Task. Idempotent
  symlink wirer; default checkout at ~/.local/share/skills/ on every
  host; SKILLS_REF env var pins a tag (default: main).
- .gitea/workflows/release.yml — auto-tag every push to main by
  Bump-Type footer (major/minor/patch, default patch). Skipped when
  commit contains [skip-release].
- README — usage, versioning, contribution flow, secret-hygiene rule.

Phase 1 wires Claude Code only (~/.claude/skills/<name> global +
<repo>/.claude/skills/<name> per-repo). Phase 2 adds Crush, opencode,
antigravity, and gitea-resident agents (cobalt-dingo, agentsquad)
once their skill conventions are researched.

Public repo, markdown-only — no secrets, no client names. Verified
via pre-push grep before initial push.

[skip-release]
2026-05-24 14:59:54 +02:00

9.4 KiB

name, description
name description
refactoring Systematic refactoring methodology based on Martin Fowler's catalog. Apply during the REFACTOR phase of TDD, never while making code changes that modify behavior.

Refactoring

Core Principle

"Refactoring is a controlled technique for improving the design of existing code. Its essence is applying a series of small behavior-preserving transformations." — Martin Fowler

The First Rule: Never refactor without tests. Tests are your safety net. If tests don't exist, write them first (load tdd skill).

The Second Rule: Each refactoring step must keep tests green. If a step breaks tests, undo it.

Never mix refactoring and behavior change. Separate commits for each.

When to Refactor

  • After the GREEN phase of TDD (REFACTOR phase)
  • When code smell makes a feature hard to add
  • When the Boy Scout Rule demands it — you're in the area, leave it better
  • When technical debt is blocking velocity

When NOT to refactor:

  • Code that works and won't change
  • Code being replaced soon
  • When you don't have tests

The Process

Step 1: Ensure Test Coverage

go test ./...          # All tests pass first
go test -race ./...    # Race detector clean

If tests don't exist, write them before refactoring. See the tdd skill.

Step 2: Identify the Target

Load the code-review skill to detect smells. Prioritize:

  1. Architectural smells — Shotgun Surgery, Divergent Change, God Objects
  2. Design smells — Long Methods, Feature Envy, Primitive Obsession
  3. Readability smells — naming, comments, duplication

Step 3: Apply Techniques in Small Steps

Each technique below is one small step. Make the change. Run tests. Commit if green. Repeat.

Step 4: Verify After Each Step

go test ./...
go test -race ./...

If red: undo the step (git checkout), understand why, try a smaller step.

Technique Catalog

Composing Methods

Extract Function When: a code block can be named meaningfully; a function does multiple things.

// Before
func processOrder(o *Order) {
    // validate
    if o.Items == nil { panic("no items") }
    // calculate
    total := 0.0
    for _, item := range o.Items {
        total += item.Price
    }
    // save
    db.Save(o, total)
}

// After: each step extracted
func processOrder(o *Order) {
    validateOrder(o)
    total := calculateTotal(o.Items)
    saveOrder(o, total)
}

Cross-reference: when naming extracted functions, load clean-code skill for naming conventions.

Inline Function When: a function's body is as clear as its name; a function only does what its name says.

Extract Variable When: a complex expression is hard to read inline.

// Before
if user.Subscription.Level >= 2 && !user.IsBanned && user.VerifiedAt != nil { ... }

// After
canAccessPremium := user.Subscription.Level >= 2 && !user.IsBanned && user.VerifiedAt != nil
if canAccessPremium { ... }

Replace Temp with Query When: a temporary variable holds the result of an expression used once.

Moving Features Between Types

Move Method When: Feature Envy detected — a method uses another type's data more than its own.

// Before: Order envies Customer
func (o *Order) getShippingCost() float64 {
    if o.Customer.Country == "SE" { return 0 }
    return 50
}

// After: moved to Customer
func (c *Customer) ShippingCost() float64 {
    if c.Country == "SE" { return 0 }
    return 50
}

Extract Type When: a cluster of methods and fields have a different responsibility than the rest of the type.

Inline Type When: a type isn't doing enough to justify its existence (Lazy Class smell).

Hide Delegate When: callers access objects through chains (a.B().C()).

Remove Middle Man When: a type only delegates without adding value.

Organizing Data

Replace Data Value with Object When: Primitive Obsession detected — a string is being used for email, user ID, currency.

// Before
type User struct {
    Email string
}

// After: Email type carries validation and prevents misuse
type Email struct {
    value string
}

func NewEmail(s string) (Email, error) {
    if !strings.Contains(s, "@") {
        return Email{}, fmt.Errorf("invalid email: %q", s)
    }
    return Email{value: s}, nil
}

type User struct {
    Email Email
}

Replace Magic Number with Constant

// Before
if retries > 3 { ... }

// After
const maxRetries = 3
if retries > maxRetries { ... }

Simplifying Conditionals

Decompose Conditional When: complex conditionals obscure intent.

// Before
if plan.ExpiresAt.Before(time.Now()) && plan.GracePeriodDays > 0 { ... }

// After
if plan.IsExpiredWithGrace() { ... }

Replace Nested Conditional with Guard Clauses When: deep nesting; use early returns instead.

// Before: arrow code
func process(o *Order) error {
    if o != nil {
        if len(o.Items) > 0 {
            if o.Customer != nil {
                // actual logic
            }
        }
    }
    return nil
}

// After: guard clauses
func process(o *Order) error {
    if o == nil {
        return errors.New("nil order")
    }
    if len(o.Items) == 0 {
        return errors.New("empty order")
    }
    if o.Customer == nil {
        return errors.New("no customer")
    }
    // actual logic
    return nil
}

Replace Conditional with Polymorphism When: repeated switch/if-else on a type string. Replace with an interface. See the solid skill, OCP section for full example.

Introduce Null Object When: repeated nil checks for a dependency.

Simplifying Function Calls

Rename Method/Variable The most powerful refactoring. When you understand what something does, give it a name that reflects that understanding. Cross-reference: load clean-code skill for naming conventions.

Introduce Parameter Object When: Long Parameter List smell — multiple parameters that always appear together.

// Before
func Search(query string, page, pageSize int, sortBy string, ascending bool) Results { ... }

// After
type SearchOptions struct {
    Query     string
    Page      int
    PageSize  int
    SortBy    string
    Ascending bool
}

func Search(opts SearchOptions) Results { ... }

Separate Query from Modifier (Command-Query Separation) When: a function both changes state and returns data.

// Bad: modifies AND returns
func (s *Stack) PopAndReturn() (Item, bool) {
    // removes and returns
}

// Good: separate
func (s *Stack) Peek() (Item, bool) { ... } // query only
func (s *Stack) Pop() bool { ... }          // command only

Dealing with Generalization

Extract Interface When: you have multiple implementations or want to enable testing with a test double.

// Before: depends on concrete type
type Service struct {
    db *postgres.DB
}

// After: depends on interface
type Store interface {
    Save(ctx context.Context, u User) error
}

type Service struct {
    store Store
}

Replace Inheritance with Delegation (Go: Embedding → Explicit Delegation) When: embedding is used but only some methods of the embedded type are needed.

Refactoring Sequence Dependencies

Apply in this order to avoid breaking changes:

  1. Extract Variable → then Extract Function → then Move Function
  2. Encapsulate Field → before other data refactorings
  3. Extract Function → before Move Function
  4. Rename → before Extract Interface

Go-Specific Refactoring Notes

Extracting interfaces

In Go, define the interface at the point of use, not at the implementation:

// The service package defines what it needs
package service

type UserStore interface {
    GetByID(ctx context.Context, id UserID) (User, error)
}

Error handling during refactoring

When extracting functions, preserve the error wrapping chain:

// Extracted function must wrap errors with its context
func validateOrder(o *Order) error {
    if len(o.Items) == 0 {
        return errors.New("order has no items")
    }
    return nil
}

// Caller wraps the context
if err := validateOrder(o); err != nil {
    return fmt.Errorf("process order: %w", err)
}

Struct options pattern (for long parameter lists)

Go idiom for optional parameters:

type ServerOption func(*Server)

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

func NewServer(addr string, opts ...ServerOption) *Server {
    s := &Server{addr: addr, timeout: 30 * time.Second}
    for _, opt := range opts {
        opt(s)
    }
    return s
}

Refactoring Commit Strategy

Refactoring commits should be separate from feature commits:

  • refactor: extract validateOrder from processOrder
  • refactor: replace string email with Email value type
  • refactor: rename UserData to UserProfile

Never combine: feat: add discount + refactor: extract discount calculator

Verification Checklist

After each refactoring session:

  • go test ./... passes
  • go test -race ./... passes
  • No behavior change (same inputs produce same outputs)
  • Committed with a refactor: prefix commit message
  • Smells addressed are documented (if doing a review)

Cross-References

  • When applying Extract Function: load clean-code skill for naming conventions
  • When introducing interfaces: load solid skill for DIP and ISP guidance
  • When detecting smells: load code-review skill
  • Source: Martin Fowler, Refactoring: Improving the Design of Existing Code (2nd ed.)
  • Full catalog: https://refactoring.guru/refactoring