safe-refactoring-mechanics
verified3a3333d0-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 file
# 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.