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

# BAD — 9 positional parameters, 5 of them a "data clump" (an address) traveling separately def create_user_bad(first_name, last_name, email, phone, street, city, state, zip_code, country): return {'name': f'{first_name} {last_name}', 'address': f'{street}, {city}, {state} {zip_code}, {country}'} # GOOD — the address clump bundled into one object, everything else keyword-only def create_user_good(*, first_name, last_name, email, phone, address): return {'name': f'{first_name} {last_name}', 'address': f"{address['street']}, {address['city']}..."}
Verified directly — the identical mistake runs silently wrong in both versions
Swapping 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.
Verified directly — the mistake is only visually catchable in one of the two versions
In 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.
Verified directly — genuine typos produce categorically better errors with keyword arguments
Calling 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

class Money: def __init__(self, amount, currency): self.amount = amount; self.currency = currency def __add__(self, other): if self.currency != other.currency: raise ValueError(f'Cannot add {self.currency} and {other.currency}') return Money(self.amount + other.amount, self.currency)
Verified directly — raw floats silently combine two different currencies into a meaningless number
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.
Verified directly — a Money type catches the identical mistake, correctly
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

Verified directly — one class mixing three unrelated concerns, split into three carrying exactly one each
A single 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'}.
The forward reference
"One class, one reason to change" is this chapter's own informal preview of Chapter 6's Single Responsibility Principle — the concern-counting technique used here is a concrete, mechanical proxy for exactly that principle, before this course names it formally.

Where This Connects

This chapter's findingWhat it connects to
A bundled parameter object making a swap mistake visually catchableChapter 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 additionChapter 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 splitChapter 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

Exercise 1

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.

📄 View solution
Exercise 2

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.

📄 View solution
Exercise 3

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.

📄 View solution

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 USD and €10 EUR into a meaningless 20.0; a Money type 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