Skip to main content

The Two Hats: Separate Structural and Behavior Changes

What This Concept Is​

The two-hats discipline separates structural work from intentional behavior changes. Under the refactoring hat, preserve the declared public observations. Under the feature or repair hat, state the intended difference and add evidence for it.

Switch deliberately, often in small steps. Tests may be added to characterize missing cases; tests tied to obsolete internal helpers may need restructuring while public assertions remain stable. This is a reasoning discipline, not a ban on improving tests.

Why It Matters Here​

If an extraction also changes rounding, a failure has two plausible causes. Separate the transformations and their evidence to make review and diagnosis easier.

A mixed commit remains bisectable: Git can identify it as the first failing revision. Bisect does not determine which edit inside it caused the failure. Small coherent checkpoints narrow that remaining investigation. One PR may contain separate preparatory refactors and feature commits.

Concrete Example​

Mixed (bad):

commit: "refactor extract priceOrder and fix negative-discount bug"

Unmixed (good):

commit A: "refactor: extract priceOrder helpers (no behavior change)"
commit B: "fix: clamp discount to zero for quantity < 500"

If commit B introduces a new regression a week later, git bisect lands directly on B. If the commit was mixed, bisect lands on the mixed commit and you still have to isolate which edit inside that commit caused the regression.

Common Confusion / Misconception​

A PR containing both kinds of work is not inherently defective. Make each stage and intended behavior difference reviewable. A small cohesive change may reasonably combine edits when separating them creates misleading or invalid intermediate states; explain the choice.

Do not rewrite shared history merely to satisfy this lesson. Practise splitting a synthetic patch or use a private practice branch. Never invent passing intermediate results.

How To Use It​

  1. State the observation you are preserving or intentionally changing.
  2. Establish a passing baseline; add missing public-boundary cases.
  3. Make a small transformation and rerun relevant checks.
  4. Save coherent, reproducible checkpoints. Separate repairs from structural changes where practical.
  5. Record any changed assertions and why they represent an approved contract change.

The repository's final history may squash checkpoints; preserve the learning evidence separately.

Check Yourself​

  1. You are renaming a method. Midway you spot a genuine bug. What do you do right now?
  2. A PR contains an extraction and new logging. How would you make the two kinds of evidence reviewable?
  3. Can a refactor commit ever add a test? When?

Mini Drill or Application​

Use the supplied invoice baseline. Plan an extraction followed by a change from per-line rounding to aggregate rounding. Write two patch descriptions and show the exact expected difference for two 0.335 lines. Work on a private copy; the baseline remains frozen. Explain what a mixed commit would and would not prevent Git bisect from finding.

Engineering Extension: Keep honest checkpoints​

The kit includes baseline, a first extraction and a final reference. These are authored snapshots, not fabricated historical commits. Preserve your own diffs and commands. A single PR may contain both hats; mark the boundary and the new expected output. Never amend a frozen baseline so a regression disappears.

Use the behavior contracts, labs and practice feedback to test the claim.

Read This Only If Stuck​

Locate this move through the Refactoring book index. The selective reading guide gives exact consulted local locators and primary documentation. Index links are not exact chapter links.