Code Smells II: Couplers & Dispensables
Clean Code, SOLID & Refactoring
Chapter 5 · Code Smells II: Couplers & Dispensables
Where Chapter 4's Bloaters were about things growing too large, this chapter's five smells are about the wrong things being connected — coupling that shouldn't exist, and code that shouldn't still exist. Each one is verified directly, tying back to Software Architecture Fundamentals Chapter 5's own coupling and cohesion criteria.
Feature Envy: a Method More Interested in Someone Else's Data
print_invoice's own source: 0 references to self., at least 1 reference to order. — InvoicePrinter has no state of its own, and this method does nothing but reach into Order's own data to do its job. The fix moves the calculation onto Order itself; the corrected print_invoice makes exactly one call, order.calculate_total(), with 0 direct references to order.items — both versions compute the identical "Total: $35.00".
Inappropriate Intimacy: Reaching Into What's Supposed to Be Private
True for a $15,000 balance before any change. BankAccount then undergoes a real, self-contained internal rename — _balance becomes _current_balance, with get_balance() updated to match. Afterward: AccountAuditorBad.audit() breaks — AttributeError: 'BankAccountBadRenamed' object has no attribute '_balance'. AccountAuditorGood.audit() still correctly returns True — completely unaffected, because it never depended on the private field's own name.
Duplicated Code: Two Copies, One Fix
validate_email_v1 and validate_email_v2 start byte-identical (copy-pasted), and agree — both incorrectly accept "@b.com" (no local part before the @). A real bug fix is applied to v1 only. Afterward: validate_email_v1_fixed("@b.com") correctly returns False; validate_email_v2("@b.com") — the un-updated copy — still incorrectly returns True. Nothing broke, no error was raised; the two copies simply, silently disagree now.
Dead Code: Verified, Not Assumed
calculate_shipping(: found, True. Searching the identical codebase for a call to calculate_shipping_legacy( — a function that exists, has a plausible name, and could easily be mistaken for still in use — finds nothing: False. It's not that this function is hard to find a use for; a real, mechanical search across the actual codebase confirms there isn't one.
Speculative Generality: A Hook Nobody Ever Pulls
discount_strategy: 0 do. The parameter exists, adds a branch to read and reason about, and has never once been exercised with anything other than its own default.
Where This Connects
| This chapter's finding | What it connects to |
|---|---|
| Feature Envy's own zero-self-reference measurement | Software Architecture Fundamentals Chapter 5's own coupling/cohesion criteria — the identical underlying question, applied to methods instead of services |
| A private-field rename breaking one audit and not the other | Software Architecture Fundamentals Chapter 7's own dependency inversion finding — depending on a public interface instead of a private implementation detail is the same discipline, one level down |
| Duplicated code diverging silently after one fix | Software Architecture Fundamentals Chapter 8's own $0.50 thick-client discrepancy — the same risk, caught here before real logic had drifted apart |
Hands-On Exercises
Write a second method on InvoicePrinter, print_item_count(order), that returns len(order.items). Verify whether this new method also shows Feature Envy by this chapter's own self-vs-order reference count, and explain whether moving it onto Order would be as clearly justified as moving print_invoice's own logic was.
Fix this chapter's own duplicated-code problem properly: delete validate_email_v2 entirely, and have every caller use validate_email_v1_fixed instead. Verify there is now only one implementation to keep correct, and confirm both original callers still get the correctly-fixed behavior.
Add a fifth call site to this chapter's own calculate_price codebase that does pass a real discount_strategy function. Re-run this chapter's own speculative-generality check across all 5 call sites, and explain whether the parameter is still speculative generality once it has a genuine use.
Chapter 5 Quick Reference
- Feature Envy, verified: a method with 0 references to its own class's data and at least 1 to another class's — fixed by moving the logic to the class it actually depends on
- Inappropriate Intimacy, verified: a private-field rename broke a class reaching directly into it; a class using only the public interface survived unaffected
- Duplicated Code, verified: two identical copies silently diverged after only one received a real bug fix — the same failure mode Software Architecture Fundamentals verified at a larger scale
- Dead Code, verified: a real codebase-wide search confirmed one function referenced, one genuinely not
- Speculative Generality, verified: a flexibility hook never once used with a non-default value across 4 real call sites
- Next chapter: SOLID I — Single Responsibility & Open/Closed, formalizing several of these smells' own fixes