Function Design
Design small, focused functions that do one thing well and are easy to test
You are an expert in function design for writing clean, maintainable code.
## Key Points
- **Do one thing**: A function should perform a single, well-defined task. If you can extract another meaningful function from it, it is doing more than one thing.
- **One level of abstraction**: Statements within a function should all be at the same level of abstraction. Do not mix high-level policy with low-level detail.
- **Small size**: Functions should typically be 5 to 15 lines. If a function exceeds 20 lines, look for extraction opportunities.
- **Minimal arguments**: Prefer zero to two arguments. Three or more suggest the arguments should be grouped into an object.
- **No side effects**: A function that claims to do one thing should not secretly do something else. If side effects are necessary, name the function to make them explicit.
- **Command-query separation**: A function should either change state (command) or return information (query), not both.
- Name functions with verbs that describe what they do, not how
- A function that returns a boolean should read as a question: `is_valid()`, `has_access()`
- Prefer returning values over mutating arguments
- Use early returns to eliminate nested conditionals
- Keep functions pure where possible — same input always produces same output
- **Hidden side effects**: A `validate()` function that also modifies the object it validates
## Quick Example
```typescript
function createUser(
name: string, email: string, age: number,
role: string, department: string, startDate: Date
): User { ... }
```skilldb get clean-code-skills/Function DesignFull skill: 198 linesFunction Design — Clean Code
You are an expert in function design for writing clean, maintainable code.
Core Philosophy
A well-designed function is a unit of thought. It takes a clearly named concept from the problem domain and gives it a precise, testable implementation. When functions are small, focused, and operate at a single level of abstraction, the code reads like a narrative: high-level functions describe what the program does in business terms, and each layer below fills in progressively more technical detail. This top-down readability is not a luxury — it is what makes code maintainable by humans who did not write it.
The most important constraint on function design is doing exactly one thing. "One thing" does not mean "one line" — it means one level of abstraction, one reason to exist, one concept that the function name can honestly describe. If you find yourself struggling to name a function without using "and" or "then," it is doing more than one thing. If you can extract a meaningful sub-function from it, the original was mixing abstraction levels.
Side effects are the hidden cost of bad function design. A function named validateOrder that also saves to the database and sends an email is lying about what it does. Every caller is now coupled to those hidden behaviors, and testing requires mocking infrastructure that the function's signature never mentions. Clean functions make their effects explicit: either through return values, through names that honestly describe the mutation (saveAndNotify), or by separating queries from commands entirely.
Anti-Patterns
-
Boolean flag parameters that fork the function into two behaviors: A parameter like
isAdminthat causes the function to execute completely different logic paths is a sign that two separate functions are being forced into one. Split them and let the caller choose the right function explicitly. -
Functions with more than three parameters: Long parameter lists are hard to remember, easy to get wrong (especially when multiple parameters share the same type), and signal that the function is doing too much. Group related parameters into an object or struct.
-
Mixing abstraction levels within a single function: A function that calls
fetchUserFromDatabase()on one line and manually parses a date string on the next is forcing the reader to context-switch between high-level intent and low-level detail. Extract the low-level work into a named helper. -
Returning null to signal absence: Returning null forces every caller to add a null check, and a forgotten check produces a null reference error far from the actual cause. Use exceptions for unexpected absence, empty collections for expected empty results, or option/result types for explicitly optional values.
-
Functions with hidden side effects: A function named
getUserthat also increments a counter, writes to a cache, or logs analytics data is violating the principle of least surprise. Side effects should be visible in the function's name and signature, not discovered through debugging.
Overview
Functions are the first line of organization in any program. Well-designed functions are small, do one thing, operate at a single level of abstraction, and have clear inputs and outputs. They are the building blocks of readable, testable, and maintainable software.
Core Principles
- Do one thing: A function should perform a single, well-defined task. If you can extract another meaningful function from it, it is doing more than one thing.
- One level of abstraction: Statements within a function should all be at the same level of abstraction. Do not mix high-level policy with low-level detail.
- Small size: Functions should typically be 5 to 15 lines. If a function exceeds 20 lines, look for extraction opportunities.
- Minimal arguments: Prefer zero to two arguments. Three or more suggest the arguments should be grouped into an object.
- No side effects: A function that claims to do one thing should not secretly do something else. If side effects are necessary, name the function to make them explicit.
- Command-query separation: A function should either change state (command) or return information (query), not both.
Implementation Patterns
Long Function — Before
def process_order(order):
# Validate
if not order.items:
raise ValueError("Order must have items")
if not order.customer:
raise ValueError("Order must have a customer")
for item in order.items:
if item.quantity <= 0:
raise ValueError(f"Invalid quantity for {item.name}")
# Calculate totals
subtotal = sum(i.price * i.quantity for i in order.items)
tax = subtotal * 0.08
shipping = 5.99 if subtotal < 50 else 0
total = subtotal + tax + shipping
# Save
order.subtotal = subtotal
order.tax = tax
order.shipping = shipping
order.total = total
order.status = "confirmed"
db.save(order)
# Notify
email.send(order.customer.email, f"Order confirmed: ${total:.2f}")
return order
Extracted Functions — After
def process_order(order):
validate_order(order)
totals = calculate_totals(order.items)
confirm_order(order, totals)
notify_customer(order)
return order
def validate_order(order):
if not order.items:
raise ValueError("Order must have items")
if not order.customer:
raise ValueError("Order must have a customer")
for item in order.items:
if item.quantity <= 0:
raise ValueError(f"Invalid quantity for {item.name}")
def calculate_totals(items):
subtotal = sum(i.price * i.quantity for i in items)
tax = subtotal * 0.08
shipping = 5.99 if subtotal < 50 else 0
return Totals(subtotal=subtotal, tax=tax, shipping=shipping)
def confirm_order(order, totals):
order.subtotal = totals.subtotal
order.tax = totals.tax
order.shipping = totals.shipping
order.total = totals.total
order.status = "confirmed"
db.save(order)
def notify_customer(order):
email.send(order.customer.email, f"Order confirmed: ${order.total:.2f}")
Too Many Parameters — Before
function createUser(
name: string, email: string, age: number,
role: string, department: string, startDate: Date
): User { ... }
Parameter Object — After
interface CreateUserRequest {
name: string;
email: string;
age: number;
role: string;
department: string;
startDate: Date;
}
function createUser(request: CreateUserRequest): User { ... }
Flag Arguments — Before
def render_page(page, is_admin):
if is_admin:
render_admin_toolbar(page)
render_admin_content(page)
else:
render_user_content(page)
Separate Functions — After
def render_admin_page(page):
render_admin_toolbar(page)
render_admin_content(page)
def render_user_page(page):
render_user_content(page)
Stepdown Rule
Arrange functions so the code reads top-down. Each function is followed by the functions it calls, at the next level of abstraction.
# Level 0 — orchestration
def generate_monthly_report(month):
data = gather_report_data(month)
summary = summarize(data)
return format_report(summary)
# Level 1 — mid-level operations
def gather_report_data(month):
transactions = fetch_transactions(month)
returns = fetch_returns(month)
return merge(transactions, returns)
# Level 2 — low-level details
def fetch_transactions(month):
return db.query("SELECT * FROM transactions WHERE month = ?", month)
Best Practices
- Name functions with verbs that describe what they do, not how
- A function that returns a boolean should read as a question:
is_valid(),has_access() - Prefer returning values over mutating arguments
- Use early returns to eliminate nested conditionals
- Keep functions pure where possible — same input always produces same output
Common Pitfalls
- Hidden side effects: A
validate()function that also modifies the object it validates - Boolean flag parameters: They signal the function does two things — split it instead
- Output arguments: Passing an object to be mutated is less clear than returning a new value
- Deeply nested logic: More than two levels of indentation usually means the function should be decomposed
- Dead parameters: Arguments that are accepted but never used; remove them
- Returning error codes instead of throwing: Leads to deeply nested
if/elsechains at every call site
Install this skill directly: skilldb add clean-code-skills
Related Skills
Code Smells
Identify and fix common code smells that indicate deeper design problems
Dependency Management
Manage dependencies and reduce coupling to build modular, flexible systems
Error Handling
Implement clean error handling strategies that keep code readable and robust
Naming Conventions
Choose clear, intention-revealing names for variables, functions, classes, and modules
Refactoring Patterns
Apply common refactoring patterns to improve code structure without changing behavior
Solid Principles
Apply SOLID principles to design flexible, maintainable object-oriented code