Code Smells & When (Not) to Refactor
Duplicated Code
The same logic in multiple places means a bug fix must be applied everywhere it's duplicated — easy to miss one spot. DRY-motivated refactorings (Extract Method, pulling shared logic into one place) address this directly.
Feature Envy & Move Method
When a method is more interested in another class's data than its own, that logic probably belongs on the other class — Move Method relocates it, improving cohesion.
Primitive Obsession
// BAD — raw float, no invariants enforced
function chargeCard(amountInCents) { ... }
// GOOD — a dedicated value object encapsulates the concept
class Money {
constructor(cents) { this.cents = cents; }
add(other) { return new Money(this.cents + other.cents); }
}Long Method & Replace Conditional with Polymorphism
A long, do-everything method signals Extract Method opportunities. A large type-based switch/if-else chain signals Replace Conditional with Polymorphism — subclasses handle their own case via overriding, connecting directly to the Open/Closed Principle: extend via a new subclass, don't modify the existing conditional.
Refactoring vs. Rewriting
Refactoring preserves verified behavior through small, test-checked steps. A full rewrite discards the existing implementation, risking loss of subtle correct edge-case handling the original quietly got right — a commonly cited reason to favor incremental refactoring over "rewrite from scratch."
When NOT to Refactor
Stable, rarely-touched code with no active problems may not justify the (however small) risk/cost of refactoring it. Refactoring delivers the most value on code being actively modified or actually causing friction — not as a blanket mandate applied everywhere regardless of benefit. This connects to technical debt: refactoring is the practical tool for paying it down where it's actually accruing interest.
Keep your own version of these notes — editable, searchable, and organised by your stack.
Start free