Code Smells I: Bloaters
Clean Code, SOLID & Refactoring
Chapter 4 · Code Smells I: Bloaters
"Bloaters" are the code smells that grow slowly, one reasonable-looking addition at a time, until a function, class, or parameter list is carrying more than any one thing should. This chapter names and verifies four of the classic five — the fifth, Long Method, was already measured directly in Chapter 3.
Long Method — Already Measured
Chapter 3's own process_order_mixed is a textbook Long Method: 5 low-level operations tangled with 1 high-level call, all inside one function body. That chapter's own single-level-of-abstraction split (calculate_order_total, log_order, send_confirmation_email) is the standard fix — nothing new to verify here; the finding already stands.
Long Parameter List & Data Clumps: a Swap Bug, Visible or Not
city and state at the call site: create_user_bad('Jane', 'Doe', ..., '123 Main St', 'TX', 'Austin', '78701', 'USA') runs without error and produces the address "123 Main St, TX, Austin 78701, USA" — genuinely wrong, silently. The identical mistake in the bundled version, address={'city': 'TX', 'state': 'Austin', ...}, produces the exact same wrong address string. Neither version's own type system catches this — Python has no way to know a two-letter string "should" be a state and not a city.
create_user_bad's own call, 'TX', 'Austin' gives a reader nothing to go on — two adjacent strings, no labels, no way to tell which was meant for which without counting positions against the function's own signature. In address={'city': 'TX', 'state': 'Austin', ...}, the mistake is labeled directly at the point of error — 'city': 'TX' reads as wrong on sight to anyone who knows TX is a state abbreviation, with zero need to consult the function's own definition.
create_user_bad with one positional argument missing raises TypeError: create_user_bad() missing 1 required positional argument: 'country' — a real error, but one that gives no information about which of the 9 positions the caller actually got wrong if the count happened to still be correct. Calling create_user_good with a typo'd keyword (first_nam instead of first_name) raises TypeError: create_user_good() got an unexpected keyword argument 'first_nam'. Did you mean 'first_name'? — Python itself suggests the fix.
Primitive Obsession: When a Raw Number Isn't Enough
sum([10.0, 10.0]), where one 10.0 represents $10 USD and the other €10 EUR: returns 20.0, without error — a number with no honest meaning, since dollars and euros were never the same unit.
Money(10.0, 'USD') + Money(10.0, 'EUR') correctly raises ValueError: Cannot add USD and EUR. Same-currency addition still works correctly — Money(10.0, 'USD') + Money(15.0, 'USD') returns 25.0 USD. The type itself now enforces a rule raw floats structurally cannot express.
Large Class: Counting Concerns, Not Just Lines
UserManagerBad class with 6 methods, classified by which concern each name touches (authentication, email, reporting): 3 distinct concerns present in one class. Splitting into Authenticator, EmailService, and ReportGenerator — each keeping only the methods matching its own name — leaves each new class with exactly 1 concern: Authenticator → {'auth'}, EmailService → {'email'}, ReportGenerator → {'report'}.
Where This Connects
| This chapter's finding | What it connects to |
|---|---|
| A bundled parameter object making a swap mistake visually catchable | Chapter 2's own naming findings — labeling data at the point of use is another instance of a name doing real diagnostic work |
| A Money type rejecting a mismatched-currency addition | Chapter 6's Single Responsibility Principle — a type that enforces its own invariant is a small, concrete instance of "one reason to change" |
| A large class's own concerns, mechanically counted and split | Chapter 5's Couplers & Dispensables — a God object is frequently also where Feature Envy and Inappropriate Intimacy show up, covered directly next chapter |
Hands-On Exercises
Using this chapter's own create_user_good, construct a call where the bundled address dict itself is missing the 'zip_code' key entirely. Verify what actually happens when the function tries to format it, and compare that failure mode to what a missing positional argument does in create_user_bad.
Add a __sub__ method to this chapter's own Money class, with the identical currency check __add__ uses. Verify subtracting two same-currency amounts works correctly, and verify subtracting two different currencies is correctly rejected.
Add a seventh method, log_login_attempt, to this chapter's own UserManagerBad, and run this chapter's own concern classifier against it. Verify whether the classifier recognizes it as belonging to any existing concern, or reveals a fourth, unaccounted-for one — and explain what this means for where the new method should actually live.
Chapter 4 Quick Reference
- Long Method: already measured in Chapter 3 — 5 low-level operations tangled with 1 high-level call
- Long Parameter List / Data Clumps, verified: a swap mistake ran silently wrong in both a 9-parameter and a bundled version, but was only visually catchable once labeled by keyword; typo'd keywords produced far better errors than a missing positional argument
- Primitive Obsession, verified: raw floats silently combined
$10 USDand€10 EURinto a meaningless20.0; aMoneytype correctly rejected the identical mistake - Large Class, verified: one class mixing 3 concerns, split into three classes each carrying exactly 1
- Next chapter: Code Smells II: Couplers & Dispensables — Feature Envy, Inappropriate Intimacy, dead code, and duplicated code