safe-refactoring-mechanics

verified

3a3333d0-9153-4d89-9fe3-780681ad726d

Use when changing code structure without changing behavior — the mechanical refactoring catalog (extract, rename, inline, move), small-step discipline, tests stay green throughout.

Metadata

Skill ID
3a3333d0-9153-4d89-9fe3-780681ad726d
Version
1
Owner
387274b7-2891-478b-81b8-e11d5adb9319
Tags
refactoringcode-qualitymaintainabilitysafety
Signature
verified
Integrity
OK
Content hash
0693cd63fa0834c90422d9c6a11090caea28a45c4e9880e5e31511c9d46f5a63
Created
2026-08-15T05:27:16Z

Skill file

Raw skill file (markdown source)
# Safe Refactoring Mechanics

**Use when** you need to improve code structure without changing behavior. The golden rule: refactorings change structure, NEVER observable behavior. Tests must stay green throughout.

## The Golden Rule

> A refactoring is a change that preserves behavior. If behavior changes, it's a feature or a bugfix — not a refactoring. Keep them in separate commits.

```
REFACTOR (structure only, tests stay green)
  ≠
FEATURE/BUGFIX (behavior changes, tests change)
```

## The Refactoring Catalog (with IDE commands)

| Refactoring | What it does | IDE (VS Code / PyCharm) |
|-------------|--------------|-------------------------|
| **Extract Function** | Pull a code block into a named function | PyCharm: `Ctrl+Alt+M` |
| **Extract Variable** | Name a complex expression | PyCharm: `Ctrl+Alt+V` |
| **Rename** | Rename symbol (safe, all references) | PyCharm: `Shift+F6` |
| **Inline** | Replace a function call with its body | PyCharm: `Ctrl+Alt+N` |
| **Move** | Move a function/class to another module | PyCharm: `F6` |
| **Change Signature** | Add/remove/reorder params | PyCharm: `Ctrl+F6` |
| **Pull Up / Push Down** | Move members up/down inheritance | PyCharm: `Ctrl+Alt+Shift+T` |

## Worked Example: Extract Function

```python
# BEFORE (long function with hidden sub-steps):
def process_order(order):
    # Calculate total
    total = 0
    for item in order.items:
        total += item.price * item.quantity
    total *= 1.08  # tax
    # Validate
    if total > 10000:
        raise ValueError("order too large")
    if not order.customer_id:
        raise ValueError("no customer")
    # Save
    db.orders.insert(order_id=order.id, total=total)
    return total

# STEP 1: Extract "calculate_total" (Ctrl+Alt+M)
def process_order(order):
    total = calculate_total(order)  # <-- extracted
    if total > 10000:
        raise ValueError("order too large")
    if not order.customer_id:
        raise ValueError("no customer")
    db.orders.insert(order_id=order.id, total=total)
    return total

def calculate_total(order):
    total = 0
    for item in order.items:
        total += item.price * item.quantity
    return total * 1.08

# Run tests after EACH extraction (tests stay green)

# STEP 2: Extract "validate_order"
def process_order(order):
    total = calculate_total(order)
    validate_order(order, total)  # <-- extracted
    db.orders.insert(order_id=order.id, total=total)
    return total
```

## The Small-Step Discipline

```bash
# One refactoring per commit, tests after each:

git commit -m "refactor: extract calculate_total"    # tests green
git commit -m "refactor: extract validate_order"     # tests green
git commit -m "refactor: rename order to purchase_order"  # tests green
```

### Why small steps?

1. If a test breaks, you know EXACTLY which refactoring caused it
2. Each commit is trivially reviewable
3. `git bisect` can pinpoint a bad refactoring instantly
4. You can revert one step without losing the others

## The Green Baseline First

```bash
# BEFORE any refactoring, establish a green baseline:
git checkout -b refactor/cleanup
pytest tests/ -x --no-header -q          # MUST be green first
git stash list                            # ensure clean working tree

# Now refactor. After each step:
pytest tests/ -x --no-header -q          # MUST stay green
```

## The 4-Step Refactoring Loop

```text
1. Run tests (confirm green)
2. Apply ONE mechanical refactoring
3. Run tests (confirm still green)
4. Commit
   └─ if tests FAILED: you changed behavior — undo and redo smaller
```

## Guardrails

- **Never mix refactoring with behavior changes in one commit.** It's unreviewable and un-revertable.
- **No green baseline = no refactoring.** If the suite is red before you start, fix that first.
- Use IDE refactorings (they're semantic, not text-replace) for renames and moves.
- If a "refactoring" makes a test fail, you accidentally changed behavior — that's a signal, not a bug in the test (usually).
- Don't refactor and fix a bug in the same PR. The reviewer can't tell what's what.

## Pitfalls

| Pitfall | Fix |
|---------|-----|
| Mixing refactor + feature in one commit | Separate commits/PRs: `refactor:` vs `feat:` |
| Refactoring on a red baseline | Green the suite first, THEN refactor |
| Giant refactor (10 functions at once) | One refactoring per commit, run tests between |
| "Improving" code while changing behavior | If a test breaks, the behavior changed — undo or split |
| Text-search-replace instead of IDE refactor | `rename` via IDE catches all references safely |

## Verify / Checklist

- [ ] Green baseline established before starting (`pytest tests/ -x`)
- [ ] Each refactoring is a single mechanical step
- [ ] Tests run after EACH step and stay green
- [ ] Each step is its own commit with a `refactor:` prefix
- [ ] No behavior changes mixed in (tests unchanged except new ones)
- [ ] `git log --oneline` shows a clean sequence of refactor commits

Attached files

No attached files.