Do One Thing
Functions that do one thing are easy to name, easy to test, and impossible to misuse. "One thing" means one level of abstraction — not one line of code. If you can extract a piece into a well-named function without it feeling contrived, your original function was doing two things.
Does many things
function processUserRegistration(data):
// validate
if not data.email or "@" not in data.email:
return { error: "invalid email" }
if not data.password or length(data.password) < 8:
return { error: "password too short" }
// hash password
hashed = hash(data.password)
// save to db
user = { email: data.email, password: hashed }
db.insert("users", user)
// send email
sendEmail(data.email, "Welcome!", "Thanks for joining.")
return { ok: true }
One thing each
function registerUser(data):
validateRegistration(data)
user = createUser(data.email, data.password)
db.save(user)
sendWelcomeEmail(user)
function validateRegistration(data):
requireValidEmail(data.email)
requireStrongPassword(data.password)
function createUser(email, password):
return { email: email, password: hash(password) }
One Level of Abstraction
Mixing high-level logic (sendEmail()) with low-level detail (hashlib) in the same function is disorienting. Readers can't tell what's important and what's implementation noise. The Step-Down Rule: every function should read like a paragraph — each line calls functions one level below it.
// ✗ Mixed levels — high-level intent buried in low-level noise
function checkout(cart, user):
total = sum of (item.price * item.qty) for each item in cart.items
tax = total * 0.08
final = total + tax
conn = db.getConnection() // ← suddenly low-level
cursor = conn.cursor()
cursor.execute("INSERT INTO orders ...", user.id, final)
conn.commit()
http.post("https://mail.svc/send", { ... }) // ← even lower
// ✓ One level — each line is at the same altitude
function checkout(cart, user):
total = calculateTotal(cart)
order = saveOrder(user, total)
notifyUser(user, order)
Arguments and Side Effects
The ideal number of function arguments is zero. One is fine. Two is acceptable. Three or more — consider a parameter object. Flag arguments (is_admin=True) are a code smell: they announce the function does two things. Side effects are lies — a function named check_password that also logs the user in breaks the single-responsibility contract and surprises every caller.
Flag arg + side effect
function getUser(userId, includeDeleted):
// does TWO things depending on flag
if includeDeleted:
return db.queryAll(userId)
return db.queryActive(userId)
function checkPassword(user, password):
if user.password == hash(password):
session.login(user) // ← surprise side effect!
return true
return false
No flags, no surprises
function getActiveUser(userId):
return db.queryActive(userId)
function getUserIncludingDeleted(userId):
return db.queryAll(userId)
function isCorrectPassword(user, password):
return user.password == hash(password)
function login(user): // side effect is explicit
session.start(user)
Code Challenge
This function does at least 4 things. Can you name each responsibility and extract it into its own function?
Key takeaway
A function should do one thing, at one level of abstraction, with a name that makes a comment unnecessary.