Reading Code Smells
Smells are triggers, not laws
Section titled “Smells are triggers, not laws”You now know how to refactor safely. The missing piece is knowing what to refactor and when. The answer is code smells: surface signs that something underneath may be poorly structured. The word smell is deliberate — a smell is a hint that invites a closer look, not a rule that demands action. Sometimes the smell is fine in context. The skill is learning to notice the smell and then decide.
flowchart LR
S["Code smell<br/>(a hint, not a rule)"] --> J{"Worth it<br/>right now?"}
J -->|yes| R["Pick a matching<br/>refactoring"]
J -->|no| N["Note it,<br/>move on"]
R --> T["Small step → test → commit"] A catalog overview
Section titled “A catalog overview”Here are the smells you will meet most often, each paired with the refactorings this course teaches to address it. Treat this as your map: when something feels wrong, name the smell, then look up the move.
| Smell | What you notice | Where this course addresses it |
|---|---|---|
| Long Function | A function that scrolls off the screen and does many things | Composing Methods — Extract Function |
| Duplicated Code | The same fragment appears in two or more places | Extract Function and Moving Features |
| Mysterious Name | A variable or function whose name hides its purpose | Extract Variable |
| Large Class | A class hoarding too many fields and responsibilities | Moving Features |
| Long Parameter List | A call with so many arguments you lose track | Simplifying APIs |
| Feature Envy | A method more interested in another object’s data than its own | Moving Features |
| Primitive Obsession | Bare strings and numbers standing in for real concepts | Organizing Data |
| Shotgun Surgery | One small change forces edits scattered across many files | Moving Features |
| Tangled Conditionals | Deeply nested or repeated branching that hides the logic | Simplifying Conditionals |
| Temporary Field / Inheritance Misuse | State used only sometimes, or hierarchies that fight you | Generalization & Inheritance |
A short example smell
Section titled “A short example smell”Here is Primitive Obsession: a raw number is passed around to mean “money,” but nothing stops a caller from mixing it up with a quantity or a temperature. The smell is the bare primitive standing in for a domain concept. The cure — wrapping it in a small type — lives in Organizing Data, but you should learn to smell it now.
// Smell: a bare number means "money" — easy to misusefunction total(price: number, shipping: number): number { return price + shipping;}
// Hint of the cure: give money its own typetype Money = { cents: number };
function totalMoney(price: Money, shipping: Money): Money { return { cents: price.cents + shipping.cents };}# Smell: a bare number means "money" — easy to misusedef total(price, shipping): return price + shipping
# Hint of the cure: give money its own typefrom dataclasses import dataclass
@dataclassclass Money: cents: int
def total_money(price: Money, shipping: Money) -> Money: return Money(price.cents + shipping.cents)// Smell: a bare number means "money" — easy to misusefunc Total(price, shipping float64) float64 { return price + shipping}
// Hint of the cure: give money its own typetype Money struct{ Cents int }
func TotalMoney(price, shipping Money) Money { return Money{Cents: price.Cents + shipping.Cents}}// Smell: a bare number means "money" — easy to misusefn total(price: f64, shipping: f64) -> f64 { price + shipping}
// Hint of the cure: give money its own typestruct Money { cents: i64,}
fn total_money(price: Money, shipping: Money) -> Money { Money { cents: price.cents + shipping.cents }}The wrapped version makes it impossible to accidentally add money to a quantity — the compiler or the type now carries the meaning that the bare number left to chance.