Behavior-preserving refactoring
Improves internal code structure through small reversible steps while retaining observable outputs, errors and effects.
behavior-preserving-refactoring
Agents using this skill
When is it useful?
Use when tangled code makes future changes difficult but current behavior must remain stable.
What does it help you do?
- Separates structural edits from requested fixes or new behavior.
- Finds prerequisite changes before attempting a larger extraction.
- Uses explicit dependencies and focused checks to expose accidental behavior changes.
Try asking this
Extract pricing calculations from our controller without changing rounding, error responses or side effects; list any separately discovered bugs. Compare totals and response bodies for the same invoice fixtures before and after extraction. Keep any intentional business-rule correction in a separate change.
Detailed documentation
Read the skill’s original instructions, examples and checks in their authored language.
Show less
Behavior-Preserving Refactoring
Refactoring changes the internal structure of code to make it easier to understand and modify without changing its observable behavior. Keep every step small enough that a broken invariant is immediately obvious and trivially reversible.
1. Two Hats: Never Mix Structure and Behavior
Apply Kent Beck's and Martin Fowler's discipline strictly:
- Structural changes (renaming, extracting helpers, moving functions, splitting modules, introducing parameter objects) must preserve exact runtime behavior, including edge-case outputs and error semantics.
- Behavioral changes (fixing a bug, adding a feature, changing validation rules or altering response formats) happen in a separate, explicitly labeled step before or after the structural refactoring.
- Make the change easy, then make the easy change: when a feature or fix is hard to fit into tangled code, first refactor to create a clean seam while keeping all tests green, then implement the behavioral change in the newly simplified structure.
2. The Mikado Method for Deep Tangled Changes
When a seemingly simple refactoring triggers a cascade of broken callers or type errors:
- State the goal clearly (e.g., "Extract pricing rules from
OrderControllerintoOrderPricingService"). - Try the naive change to discover what breaks directly (compiler errors, failing tests, circular imports).
- Record the prerequisites as leaf nodes on a dependency graph.
- Revert the broken working tree instead of piling workarounds on top of a half-broken state.
- Execute leaf prerequisites first, bottom-up, verifying that the build and tests stay green after every leaf step until the root goal succeeds cleanly.
3. Mechanics of Safe Micro-Steps
Prefer mechanical, low-risk transformations chained together over a single rewrite:
- Extract Function / Method: isolate a cohesive block with a name that states what it does. Pass read-only inputs explicitly and return computed results instead of mutating outer variables.
- Introduce Parameter Object: replace long, repeated parameter lists or loose bags of primitives with a well-named structure at the boundary.
- Replace Conditional with Guard Clauses or Polymorphism: flatten deeply nested
if/elsearrowheads into early returns first; only introduce strategy/dispatch tables when multiple call sites switch on the same type code. - Split Phase: separate parsing/input normalization, pure domain calculation and side-effectful I/O (database writes, HTTP calls, logging) into distinct sequential steps.
- Parallel Change (Expand -> Migrate -> Contract):
- Expand: add the new function, signature or module alongside the old one; delegate from the old entry point to the new implementation.
- Migrate: move callers incrementally to the new interface, running checks after each batch.
- Contract: remove the deprecated wrapper only after zero callers remain.
4. Guardrails During Execution
- Never "fix" a suspicious legacy quirk silently inside a refactoring step — verify whether callers depend on it, or flag it separately.
- Keep diffs focused on the target scope; avoid drive-by formatting or unrelated file churn that obscures review.
- Run typecheck, lint and targeted tests after every micro-step.
Included files 1
SKILL.md
