Refactoring preserves external behavior

Refactoring changes the internal organization of code, but not its external contract. Its goals are readability, less duplication, clear responsibilities, easier testing, and lower coupling.

If the result for a client changes, this is already a bugfix, feature, migration, or another type of work. Do not disguise such a change as structural cleanup.

Observable behaviorExamples
APIHTTP status, JSON, headers, and public signatures
MessagesError codes and texts
StateDatabase records, events, emails, and webhooks
OrderSequence of business checks and side effects

Invariants: what must remain unchanged

  • the same relevant inputs and outputs;
  • the same exceptions and messages;
  • the same order of checks when it affects the result;
  • the same records, events, and side effects;
  • no changes to the public API.

Safe candidates and dangerous substitutions

Can be refactoringIs not pure refactoring
Extract validation into a private helperFix an incorrect result
Rename a private methodAdd a new user scenario
Simplify conditions without changing logicUpdate a framework or dependencies
Remove duplicationRewrite the whole module or change the API

Ask Claude for analysis first

  1. Find calls, tests, dependencies, and module boundaries.
  2. Ask Claude to name local structural candidates.
  3. For each candidate, require the benefit, risk, and affected files.
  4. Choose one structural problem.
  5. Record the goal, non-goals, and invariants.
  6. Only then delegate one small step.

The one-refactoring-PR rule

One PR should solve one structural problem. Its description should explain what changed, what was deliberately not changed, and how preservation of behavior is checked.

If the goal cannot be stated without adding “and one more thing,” the changes most likely need to be split.

Canonical lesson source · JavaRush

Open JavaRush material