Functions: Size, Purity & a Single Level of Abstraction
Clean Code, SOLID & Refactoring
Chapter 3 · Functions: Size, Purity & a Single Level of Abstraction
Three properties of a function that turn out to be genuinely measurable, not matters of taste: how much it depends on and changes shared state (purity), and whether every line inside it operates at the same conceptual altitude (a single level of abstraction). Both connect directly to bugs this site has already verified elsewhere — this chapter reproduces one of them on purpose.
Purity: Why an Impure Function Reproduces a Bug You've Already Seen
30 units from a stock of 100 (expect 70), then sell 50 units from a fresh stock of 100 (expect 50). Against sell_item_impure, test 1 correctly returns 70 — but test 2 returns 20, not 50, because it silently operated on the 30-units-already-sold state test 1 left behind. This is exactly Design Patterns Chapter 2's own SingletonCounter finding, reproduced here at the plain-function level: shared mutable state makes correctness depend on call order.
sell_item_pure(100, 30) and sell_item_pure(100, 50), in the identical order: both correctly return 70 and 50. Neither call could see anything left behind by the other, because neither call reads or writes anything outside its own parameters and return value.
A Single Level of Abstraction, Measured
A function mixing "what should happen" (business steps) with "how exactly it happens" (raw arithmetic, string formatting, file I/O) forces a reader to switch mental altitude line by line.
process_order_mixed's own body for raw operators, string formatting, and file I/O versus named business-function calls: 5 low-level operations (arithmetic, an f-string, a file write) sit alongside just 1 high-level call. A reader has to switch between "what does this business rule mean" and "how exactly does string formatting work" within the same six lines.
process_order_clean's own body: 3 high-level calls, 0 low-level operations. Every line reads at the identical altitude — "calculate the total, log it, email it" — with all the raw detail pushed down into the three functions that own it individually.
$100: both process_order_mixed and process_order_clean return 80.0. Separating the levels of abstraction changed nothing about what the function computes.
Where This Connects
| This chapter's finding | What it connects to |
|---|---|
| Impurity reproducing a real test-order-dependent bug | Design Patterns Chapter 2's own SingletonCounter — the identical failure mode, verified twice in two different courses |
| A single-level function's own testability payoff | Software Architecture Fundamentals Chapter 7's own ~18,111× fake-adapter speedup — purity is the property that makes swapping in a fake safe in the first place |
process_order_mixed's own tangled responsibilities | Chapter 4's own "Bloaters" code smells — a function this tangled is a direct instance of the long-method smell that chapter names and catalogs |
Hands-On Exercises
Write a third "test" against this chapter's own sell_item_impure — selling 10 units from a fresh stock of 50 (expect 40) — run immediately after this chapter's own two tests, with no reset in between. Verify what it actually returns and explain why, tracing the state left behind by each prior call.
Add a fourth business step to process_order_clean — a call to a new award_loyalty_points(order, total) function — following the same pattern as the existing three calls. Verify the top-level function still has 0 low-level operations after the addition.
Using this chapter's own two verified findings, explain why calculate_order_total, log_order, and send_confirmation_email (the three functions process_order_clean delegates to) are each individually easier to write as pure functions than process_order_mixed ever could be — what property of mixing abstraction levels makes purity harder to achieve?
Chapter 3 Quick Reference
- Purity: a function whose result depends only on its own arguments — verified: an impure function reproduced a real test-pollution bug (
20instead of50); the pure equivalent was immune by construction - Single level of abstraction: every line in a function operating at the same conceptual altitude — verified: a mixed function tangled
5low-level operations with1high-level call; a single-level version had3high-level calls and0low-level operations, computing the identical result - Next chapter: Code Smells I: Bloaters — naming and catalogging exactly the kind of tangled function this chapter's own
process_order_mixedalready demonstrated