Functions Should Do One Thing, at One Level of Abstraction
What This Concept Is
Clean Code's first and second rules for functions:
The first rule of functions is that they should be small. The second rule of functions is that they should be smaller than that.
With two operational heuristics:
- Do one thing. A function does one thing when the reader can describe what it does in a single sentence that does not contain "and" or "then."
- One level of abstraction. A function mixes levels when some lines talk in domain language and others poke at primitives (
order.total()next toorder.items.get(0).price).
The practical shape that follows: functions that read top-down, where each line is a named operation at the same conceptual level, and lower-level details live in functions below ("the stepdown rule").
Why It Matters Here
Function-level decomposition is the smallest design decision you make hundreds of times a day. It determines whether a class is a cohesive whole or a bag of tangled procedures. Most long methods are not complicated problems; they are easy problems whose author never paused to give the steps names.
Function decomposition is also the backbone of every refactoring move in Module 2: Extract Function, Inline Function, Move Function, Replace Temp with Query. None of them work if you cannot see "one thing."
Concrete Example
Mixed levels and doing five things:
def render_report(orders, out):
total = 0
for o in orders:
for i in o.items:
total += i.price * i.qty
out.write(f"<html><body><h1>Report</h1>")
for o in orders:
out.write(f"<p>{o.id}: {o.customer}</p>")
out.write(f"<h2>Total: {total}</h2></body></html>")
Three different concerns are interleaved: totaling, HTML scaffolding, and per-order rendering. Each is at a different level of abstraction.
One thing per function, top-down:
def render_report(orders, out):
write_header(out)
write_orders(orders, out)
write_total(total_of(orders), out)
write_footer(out)
def total_of(orders):
return sum(i.price * i.qty for o in orders for i in o.items)
def write_header(out): out.write("<html><body><h1>Report</h1>")
def write_footer(out): out.write("</body></html>")
def write_orders(orders, out):
for o in orders: out.write(f"<p>{o.id}: {o.customer}</p>")
def write_total(total, out): out.write(f"<h2>Total: {total}</h2>")
render_report now reads as a four-step script. The details are one level below, in the same order they were mentioned above.
Common Confusion / Misconception
"Functions should be under N lines." Line count is a smell detector, not the rule. The real rule is "one thing at one level of abstraction." A 30-line pure function that describes one coherent calculation can be cleaner than five jagged five-liners.
A second misconception: "extracting functions always improves readability." Extraction helps when the new function can be given a name that explains itself. If the only name you can write is step2 or helper, the extraction is hiding complexity, not reducing it.
A third: "short functions mean many small files." Short functions typically live together on a class, with the caller on top and the callees below, following the stepdown rule.
How To Use It
When writing or reviewing a function, apply:
- Describe it out loud. If you say "and" or "then," there are at least two things.
- Scan for mixed levels: domain-level lines next to primitive manipulation are a tell.
- Extract the lower level into a well-named function whose body lives below the caller.
- Re-read the top-level function. It should now describe intent without distraction.
- Stop when further extraction makes the name more confusing than the body.
Check Yourself
- Why is "line count" a symptom but not the rule?
- What does the stepdown rule say about where sub-functions live?
- When is extracting a function actively harmful?
Mini Drill or Application
Pick a function in your code over 30 lines. Do all four:
- Describe it out loud and count the "ands."
- Mark each line as level
L0(caller-language) orL1(detail). Mixed = extract. - Extract each
L1run into a named function directly below the caller. - Reread the top-level function; it should read like a plan, not a procedure.