Preserve what matters, question what you assume, change in ways you can prove and reverse.
When refactoring, keep cycling through this:
- What is this code promising externally?
- What do I actually know vs assume?
- What evidence locks current behavior down?
- What is the smallest reversible improvement?
- What did this step prove or disprove?
That is a better SOP than rigid rules, because it helps when the situation is messy.
Use this prompt when you’re refactoring existing code and want to avoid breaking hidden behavior.
- Before changing legacy or messy code with unknown dependencies or weird edge cases.
- When you need safer, incremental refactors instead of rewrites.
- When preserving user-facing behavior matters more than making the code prettier.
- When you want evidence-based changes, like tests or before/after outputs, not vibes.
Do not use this when you are doing a feature change or rewrite, not a true refactor that preserves behavior.
- When you explicitly intend to change user-facing behavior or product rules.
- When you are in a firefight bug fix and need the smallest, most direct patch.
- When you lack any way to verify behavior (no tests, logs, or realistic data) and cannot safely add them first.
- When a full replacement is already cheaper and safer than incremental changes.
Assume the code does more than you think.
Old code is rarely just “bad.” It often contains:
- hidden business rules
- workarounds for past bugs
- weird behavior other parts now rely on
So the first stance is not “how do I improve this?” It is:
“What job is this code really doing today?”
The point is not prettier code. The point is to keep useful behavior while making future change cheaper.
So always separate:
- behavior = what users/callers experience
- structure = how the code is organized internally
Refactoring means: preserve behavior, improve structure
If behavior changes, that is product work, not pure refactoring.
Do not begin with internals. Begin with impact.
Ask:
- who depends on this?
- what would they notice if I broke it?
- what outputs, side effects, timing, errors, or data shapes matter?
This helps you see the true boundary.
Mental model: the outside is a promise, the inside is negotiable
Never trust “this should still work.”
Use proof:
- tests
- before/after outputs
- logs
- real examples
- observed usage
The right question is not: “Do I think this is safe?”
It is: “What evidence says this is safe?”
Messy code creates discomfort, so people rush to simplify it. That is where breakage happens.
First reduce uncertainty:
- understand flows
- map dependencies
- observe inputs/outputs
- identify what is stable vs accidental
Only then simplify.
Mental model: do not compress what you do not yet understand
A good refactor is easy to undo.
So think:
- can I change this in small steps?
- can I keep old and new paths briefly?
- can I isolate one decision at a time?
- can I roll back without drama?
This mindset naturally leads to safer moves.
Rule of thumb: if a step is hard to reverse, it is probably too big
Mixing goals causes confusion.
Examples of mixed goals:
- renaming + changing logic
- extracting functions + changing API shape
- cleanup + optimization + bug fix
When things go wrong, you lose the cause.
Better question: “What single problem am I solving in this step?”
8. Look for hidden contracts
The most dangerous part of refactoring is usually not the code you see. It is the dependency you do not see.
Ask:
- is someone relying on this exact field name?
- this error message?
- this ordering?
- this timing?
- this null behavior?
- this side effect?
A lot of “bad code” survives because it is quietly holding up something else.
Mental model: every strange line may be protecting a real-world assumption
Early in a refactor, your job is to learn. Later, your job is to improve.
So ask:
- what is this code teaching me?
- where am I still guessing?
- what small change would reveal more?
This keeps you from forcing a clean design too early.
Do not think “rewrite module.” Think:
- where can I separate concerns?
- where can I introduce a boundary?
- where can I swap one part without touching the rest?
A seam is just a place where change can happen safely.
Good refactoring is usually seam creation first, cleanup second.
Sometimes the cleanest-looking change is the riskiest one.
Example:
- adding a wrapper may feel less elegant than replacing everything directly
- duplicating code briefly may be safer than forcing abstraction too soon
Temporary ugliness is acceptable if it buys safety and clarity.
Mental model: prefer awkward transition over elegant breakage
The real test of good refactoring is not “does it look nicer?” It is:
- is the code easier to reason about?
- easier to test?
- easier to change?
- less coupled?
- less surprising?