Code Smells — A Practical Refactoring Guide

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.

The term “code smell” and this catalog come from Martin Fowler and Kent Beck’s Refactoring. This is an original practical summary for quick reference; see the book for the full treatment.

Contents

  1. What is a code smell
  2. Bloaters
  3. Object-Orientation Abusers
  4. Change Preventers
  5. Dispensables
  6. Couplers
  7. Smell-to-refactoring cheat sheet
  8. How to use smells well
  9. Summary

1. What is a code smell

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.

Five families of code smells
The catalog groups into five families. Most smells you meet in practice are one of these; the family hints at the kind of fix.

2. Bloaters

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.

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}")

3. Object-Orientation Abusers

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.

4. Change Preventers

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.

5. Dispensables

Dispensables are things whose absence would make the code cleaner. They add volume and reading cost without adding value.

6. Couplers

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.

7. Smell-to-refactoring cheat sheet

SmellCategoryWhat you seeRefactoring to apply
Long MethodBloaterA function that scrolls; many levels of detail in one place.Extract Method
Large ClassBloaterToo many fields and responsibilities in one class.Extract Class / Extract Subclass
Long Parameter ListBloaterFour or more parameters that are hard to remember.Introduce Parameter Object / Preserve Whole Object
Data ClumpsBloaterThe same group of values appears together repeatedly.Extract Class / Introduce Parameter Object
Primitive ObsessionBloaterRaw strings/ints modeling real concepts.Replace Primitive with Value Object
Switch StatementsOO abuserBranching on a type code, often repeated.Replace Conditional with Polymorphism
Refused BequestOO abuserSubclass ignores inherited members.Push Down Method / Replace Inheritance with Delegation
Temporary FieldOO abuserA field only valid part of the object’s life.Extract Class
Alternative Classes, Diff. InterfacesOO abuserTwo classes do the same job with different APIs.Rename Method / Move Method to align
Divergent ChangeChange preventerOne class changes for many unrelated reasons.Extract Class
Shotgun SurgeryChange preventerOne change forces edits across many classes.Move Method / Move Field (consolidate)
Parallel InheritanceChange preventerNew subclass here forces a new subclass there.Refer one hierarchy to the other
Duplicate CodeDispensableSame structure in multiple places.Extract Method / Pull Up Method
Dead CodeDispensableUnused variable, method, or class.Delete it
Lazy ClassDispensableA class that does too little to justify itself.Inline Class
Speculative GeneralityDispensableAbstractions for a future that never came.Collapse Hierarchy / Inline Class / Remove Parameter
Data ClassDispensableOnly fields and accessors, behavior lives elsewhere.Move Method
Feature EnvyCouplerA method uses another class’s data more than its own.Move Method
Inappropriate IntimacyCouplerTwo classes reach into each other’s internals.Move Method / Move Field / Extract Class
Message ChainsCouplerLong a.b().c().d() navigation.Hide Delegate
Middle ManCouplerA class that only delegates.Remove Middle Man

8. How to use smells well

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.

The safe refactoring loop
Refactor in a tight loop: one small structural change at a time, with a green test suite between every step.
Boy Scout rule in practice
The Boy Scout rule in four moves: while you are already in a file, make one small safe improvement and move on.

9. Summary

IdeaThe one-line takeaway
What a smell isA surface symptom of a deeper design problem — a hint, not a bug.
Heuristics, not rulesNotice the smell, then judge whether it actually hurts before you fix it.
BloatersCode grown too big — extract methods, classes, and parameter objects.
OO abusersMisused inheritance and conditionals — reach for polymorphism.
Change preventersMake change cheap by giving each reason-to-change one home.
DispensablesDelete what adds no value; duplication and dead code first.
CouplersMove behavior to the data it uses; hide long navigation chains.
Using smells wellTests protect, small steps, Boy Scout rule, review for smells.
The recurring theme: smells are a shared vocabulary for “this will be painful to change later.” They earn their keep only when paired with tests and small, disciplined refactorings — the point is always cheaper change, never cleaner code for its own sake.