A working reference to the classic code smells — the surface symptoms of deeper design trouble — and the refactorings that clear them.
A code smell is a hint. It is a pattern in your source that, more often than not, signals a design weakness worth addressing. The value of the vocabulary is speed: once a team shares names like “Feature Envy” or “Shotgun Surgery,” a reviewer can point at a problem in two words instead of a paragraph, and everyone knows roughly what fix to reach for. This guide walks the five families of smells, gives the typical refactoring for each, and ends with a cheat sheet you can keep open during a review.
A code smell is a surface symptom that usually points to a deeper design problem. It is not a bug — the program can compile, pass its tests, and ship with every smell in this guide intact. What a smell tells you is that the code will be harder than it should be to read, change, or extend later. It is a prompt to look closer, not a verdict.
The critical caveat: smells are heuristics, not rules. A long method in a hot loop may be exactly right; a tiny class that looks “lazy” may earn its keep as a domain concept. The discipline is to notice the smell, ask whether it is actually hurting you, and refactor only when the answer is yes. Treat the catalog as a checklist for your intuition, never as a linter that must be obeyed.

Bloaters are code, methods, and classes that have grown so large they are hard to work with. They rarely start big — they accrete, one “just one more line” at a time, until nobody wants to touch them.
start, end, timezone). If they always travel as a group, they want to be a class. Fix with Extract Class then Introduce Parameter Object.float, a phone number as a str). Fix by replacing the primitive with a small value object or type.The workhorse fix is Extract Method. Before — one function juggling validation, calculation, and formatting:
def print_invoice(order):
# validate
if not order.items:
raise ValueError("empty order")
# calculate
total = 0
for item in order.items:
total += item.price * item.qty
if order.customer.is_gold:
total *= 0.85
# format
print(f"Invoice for {order.customer.name}")
print(f"Total: ${total:.2f}")
After — each concern named and separated, and the top function reads like a table of contents:
def print_invoice(order):
_validate(order)
total = _order_total(order)
_render(order.customer, total)
def _validate(order):
if not order.items:
raise ValueError("empty order")
def _order_total(order):
subtotal = sum(item.price * item.qty for item in order.items)
return subtotal * 0.85 if order.customer.is_gold else subtotal
def _render(customer, total):
print(f"Invoice for {customer.name}")
print(f"Total: ${total:.2f}")
These smells are incomplete or incorrect uses of object-oriented mechanisms — polymorphism, inheritance, interfaces — where the code fights the paradigm instead of leaning on it.
switch or if/elif chain branching on a type code, especially when the same chain reappears elsewhere. Fix with Replace Conditional with Polymorphism: give each type a class and let dispatch do the branching.These smells make change expensive: one conceptual change forces edits in many unrelated places, or in the wrong number of places. They are the most direct tax on velocity.
Dispensables are things whose absence would make the code cleaner. They add volume and reading cost without adding value.
Couplers are smells of excessive coupling between classes — or, in the case of Middle Man, of coupling avoided so aggressively that a class stops pulling its weight.
a.getB().getC().getD().doThing(), coupling itself to the whole navigation structure. Fix with Hide Delegate: give the first object a method that hides the chain.| Smell | Category | What you see | Refactoring to apply |
|---|---|---|---|
| Long Method | Bloater | A function that scrolls; many levels of detail in one place. | Extract Method |
| Large Class | Bloater | Too many fields and responsibilities in one class. | Extract Class / Extract Subclass |
| Long Parameter List | Bloater | Four or more parameters that are hard to remember. | Introduce Parameter Object / Preserve Whole Object |
| Data Clumps | Bloater | The same group of values appears together repeatedly. | Extract Class / Introduce Parameter Object |
| Primitive Obsession | Bloater | Raw strings/ints modeling real concepts. | Replace Primitive with Value Object |
| Switch Statements | OO abuser | Branching on a type code, often repeated. | Replace Conditional with Polymorphism |
| Refused Bequest | OO abuser | Subclass ignores inherited members. | Push Down Method / Replace Inheritance with Delegation |
| Temporary Field | OO abuser | A field only valid part of the object’s life. | Extract Class |
| Alternative Classes, Diff. Interfaces | OO abuser | Two classes do the same job with different APIs. | Rename Method / Move Method to align |
| Divergent Change | Change preventer | One class changes for many unrelated reasons. | Extract Class |
| Shotgun Surgery | Change preventer | One change forces edits across many classes. | Move Method / Move Field (consolidate) |
| Parallel Inheritance | Change preventer | New subclass here forces a new subclass there. | Refer one hierarchy to the other |
| Duplicate Code | Dispensable | Same structure in multiple places. | Extract Method / Pull Up Method |
| Dead Code | Dispensable | Unused variable, method, or class. | Delete it |
| Lazy Class | Dispensable | A class that does too little to justify itself. | Inline Class |
| Speculative Generality | Dispensable | Abstractions for a future that never came. | Collapse Hierarchy / Inline Class / Remove Parameter |
| Data Class | Dispensable | Only fields and accessors, behavior lives elsewhere. | Move Method |
| Feature Envy | Coupler | A method uses another class’s data more than its own. | Move Method |
| Inappropriate Intimacy | Coupler | Two classes reach into each other’s internals. | Move Method / Move Field / Extract Class |
| Message Chains | Coupler | Long a.b().c().d() navigation. | Hide Delegate |
| Middle Man | Coupler | A class that only delegates. | Remove Middle Man |
Knowing the catalog is the easy part; using it without wrecking a working system is the skill. The loop below keeps refactoring safe — behavior never changes, only structure.


| Idea | The one-line takeaway |
|---|---|
| What a smell is | A surface symptom of a deeper design problem — a hint, not a bug. |
| Heuristics, not rules | Notice the smell, then judge whether it actually hurts before you fix it. |
| Bloaters | Code grown too big — extract methods, classes, and parameter objects. |
| OO abusers | Misused inheritance and conditionals — reach for polymorphism. |
| Change preventers | Make change cheap by giving each reason-to-change one home. |
| Dispensables | Delete what adds no value; duplication and dead code first. |
| Couplers | Move behavior to the data it uses; hide long navigation chains. |
| Using smells well | Tests protect, small steps, Boy Scout rule, review for smells. |