Preparatory Refactoring
Make the change easy, then make the easy change.
When to Use
You need to add a feature, but the code isn't structured to receive it cleanly. Instead of doing the restructuring and the feature in one PR, split them.
You'll know this technique applies when you find yourself thinking "I need to move this code around before I can add the new thing." That's your signal: the move IS the first PR.
The Pattern
- PR 1 — Prep: Refactor existing code to create a clean seam (extract function, rename, move file, introduce parameter). Zero behavior change. All existing tests pass unchanged.
- PR 2 — Feature: Add the new feature, which now slots in cleanly because the seam you need already exists.
The discipline is in keeping PR 1 pure. No sneaking the feature in. No "while I'm in here" changes. If the PR description says "refactor X to prepare for Y," that's all it does.
Worked Example
The engineering team needs to add email notifications when an order ships. The order processing logic lives in a single processOrder() function — validation, payment, and completion all tangled together.
We'll decompose the work into two PRs: first extract the completion logic into its own function, then add the notification in that function. The first PR is pure structure; the second PR is pure behavior.
PR 1: Extract `completeOrder()` from `processOrder()`
The order-completion logic (updating the order record and writing to the audit log) is extracted into a standalone completeOrder() function. processOrder() now delegates to it.
This is a pure structural change — the behavior is identical. Every existing test passes without modification. What we've gained is a named seam: a place where new behavior (like notifications) can be added cleanly in the next PR.
| line number | line content |
|---|
PR 2: Add email notification in `completeOrder()`
With completeOrder() isolated, adding the notification is a surgical two-part change: import the notification service, then call it at the end of completeOrder().
The diff is small because the prep work did its job. A reviewer can immediately understand what changed and verify it's correct — there's no structural noise to parse through.
| line number | line content |
|---|
Common Mistakes
Over-refactoring in the prep PR. Refactor only what the next PR needs. If you catch yourself restructuring code that the feature PR won't touch, you're gold-plating. The prep PR should be the minimum structural change that makes the feature PR trivially simple.
Skipping the PR description context. Without explanation, reviewers will be confused about why you're refactoring. Your prep PR description should explicitly say something like: "Extracting completeOrder() to prepare for adding email notifications in a follow-up PR." Give reviewers the map before they read the code.
Merging prep and feature "since I'm already in here." This defeats the entire purpose. The discipline of shipping the prep separately is what makes the feature PR trivially small and reviewable. Two small PRs are easier to review than one medium one — and if the feature PR needs to be reverted, the refactoring stays.