← Back to Desktop app developers

Skill · Version 1.0.0

Behavior-preserving refactoring

Improves internal code structure through small reversible steps while retaining observable outputs, errors and effects.

behavior-preserving-refactoring

Team members using this skill

See how it helps

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.

Details…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:

  1. State the goal clearly (e.g., "Extract pricing rules from OrderController into OrderPricingService").
  2. Try the naive change to discover what breaks directly (compiler errors, failing tests, circular imports).
  3. Record the prerequisites as leaf nodes on a dependency graph.
  4. Revert the broken working tree instead of piling workarounds on top of a half-broken state.
  5. 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/else arrowheads 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):
    1. Expand: add the new function, signature or module alongside the old one; delegate from the old entry point to the new implementation.
    2. Migrate: move callers incrementally to the new interface, running checks after each batch.
    3. 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