Functions and methods skill

Comprehensive guide to writing small, focused functions that do one thing well.

by wondelai·MIT license·★ 2,235 Stars on the repo·GitHub ↗

Use now

Files of Functions and methods

wondelai/main1 file
functions-and-methods.md
Show the full text374 lines

Functions and Methods

Comprehensive guide to writing small, focused functions that do one thing well. Based on Robert C. Martin's Clean Code, Chapters 3 and 4.

Table of Contents

  1. The First Rule of Functions
  2. Do One Thing
  3. Function Arguments
  4. Flag Arguments
  5. Command-Query Separation
  6. Side Effects
  7. Extract Till You Drop
  8. Structured Programming
  9. DRY: Don't Repeat Yourself
  10. Function Organization Within a Class
  11. Common Function Anti-Patterns

The First Rule of Functions

Functions should be small. The second rule of functions is that they should be smaller than that.

A well-written function:

  • Fits on one screen (ideally 4-10 lines)
  • Has a name that describes exactly what it does
  • Takes few arguments (zero is best, three is the maximum)
  • Has no side effects
  • Operates at a single level of abstraction

Do One Thing

Functions should do one thing. They should do it well. They should do it only.

How to Know if a Function Does One Thing

If you can extract another function from it with a name that is not merely a restatement of its implementation, the function does more than one thing.

# BAD: Does three things
def process_payment(order):
    # 1. Validate
    if not order.items:
        raise ValueError("Empty order")
    if order.total <= 0:
        raise ValueError("Invalid total")

    # 2. Charge
    payment_result = gateway.charge(order.customer.card, order.total)
    if not payment_result.success:
        raise PaymentError(payment_result.error)

    # 3. Notify
    email_service.send_receipt(order.customer.email, order)
    analytics.track("payment_completed", order.id)

# GOOD: Does one thing, delegates details
def process_payment(order):
    validate_order(order)
    charge_customer(order)
    send_notifications(order)
The Step-Down Rule

Code should read like a top-down narrative. Every function should be followed by the next level of abstraction.

To process a payment:
    We validate the order.
    We charge the customer.
    We send notifications.

To validate the order:
    We check it has items.
    We check the total is positive.

To charge the customer:
    We call the payment gateway.
    We handle any payment failure.

To send notifications:
    We email the receipt.
    We track the analytics event.

This reads like a newspaper: the headline (top-level function) tells you the story, and each successive paragraph (sub-function) provides more detail.


Function Arguments

The ideal number of arguments for a function is zero (niladic). Next comes one (monadic), followed closely by two (dyadic). Three arguments (triadic) should be avoided where possible. More than three (polyadic) requires very special justification.

Argument Count Guide
Count Name When acceptable Example
0 Niladic Simple operations getCurrentTime()
1 Monadic Asking a question or transforming input isValid(email), parse(json)
2 Dyadic Natural pairings Point(x, y), assertEquals(expected, actual)
3 Triadic Rarely; consider object Color(r, g, b)
4+ Polyadic Almost never Wrap in object: new Config(...)
Common Monadic Forms

Three common reasons to pass a single argument:

  1. Asking a question: boolean fileExists(path) -- returns true/false about the argument
  2. Transforming it: InputStream fileOpen(path) -- transforms the argument and returns the result
  3. Event: void passwordAttemptFailedNTimes(attempts) -- uses the argument to alter system state (make this clear from the name)
Why Many Arguments Are Problematic
// BAD: What does each argument mean? What order?
createReport(title, startDate, endDate, format, includeCharts, sendEmail, recipients);

// GOOD: Parameter object groups related arguments
ReportConfig config = new ReportConfig.Builder()
    .title("Q4 Revenue")
    .dateRange(startDate, endDate)
    .format(PDF)
    .includeCharts(true)
    .build();
createReport(config);

Each argument increases the difficulty of understanding, testing, and calling the function. Arguments also create ordering dependencies that the reader must memorize.


Flag Arguments

Flag arguments are ugly. Passing a boolean into a function loudly declares that the function does two things -- one thing if the flag is true, another if false.

# BAD: Flag argument
def render(document, is_for_print):
    if is_for_print:
        # 20 lines of print rendering
        ...
    else:
        # 20 lines of screen rendering
        ...

# GOOD: Two clearly named functions
def render_for_print(document):
    ...

def render_for_screen(document):
    ...

If a function must behave differently based on a condition, split it into two named functions. If the behaviors share logic, extract the shared part into a private helper.


Command-Query Separation

Functions should either do something (command) or answer something (query), but not both.

// BAD: Does this set the attribute or check if it exists?
if (set("username", "unclebob")) { ... }

// GOOD: Separate query from command
if (attributeExists("username")) {
    setAttribute("username", "unclebob");
}

Commands change the state of an object. They should return void. Queries return information about an object. They should not change state.

When a function both changes state and returns a value, the reader cannot tell from the call site what is happening.


Side Effects

A side effect is when a function promises to do one thing but also does other hidden things.

Common Hidden Side Effects
Declared purpose Hidden side effect Danger
checkPassword(user, password) Initializes a session Calling "check" unexpectedly logs user in
getUser(id) Creates user if not found "Get" implies read-only; caller doesn't expect writes
toString() Modifies internal state Debugging with print statements changes behavior
validate(input) Sends analytics event Validation during testing triggers real events
How to Fix Side Effects
  1. Make the side effect explicit in the name: checkPasswordAndInitSession()
  2. Separate the concerns: checkPassword() + initSession() as two calls
  3. Prefer option 2 -- separation makes testing easier and keeps functions honest

Extract Till You Drop

If you can extract a named function from a block of code, you should. The extracted function's name adds documentation value, even if the function is only called from one place.

When to Extract
Signal Action
A block inside an if, else, for, or while Extract to named function
A comment explaining what the next lines do Replace comment with named function
A function longer than 10 lines Look for extraction opportunities
Nested indentation deeper than 2 levels Extract inner blocks
Code that you'd need to re-read to understand Name it so you don't have to
Extraction Example
// BEFORE: Deeply nested, hard to follow
function processOrders(orders: Order[]) {
  for (const order of orders) {
    if (order.status === 'pending') {
      let total = 0;
      for (const item of order.items) {
        if (item.inStock) {
          total += item.price * item.quantity;
          if (item.quantity > 10) {
            total *= 0.9; // bulk discount
          }
        }
      }
      if (total > 0) {
        order.total = total;
        order.status = 'processed';
        db.save(order);
        emailService.sendConfirmation(order);
      }
    }
  }
}

// AFTER: Each function does one thing
function processOrders(orders: Order[]) {
  orders
    .filter(isPending)
    .forEach(processOrder);
}

function processOrder(order: Order) {
  const total = calculateOrderTotal(order);
  if (total > 0) {
    finalizeOrder(order, total);
  }
}

function calculateOrderTotal(order: Order): number {
  return order.items
    .filter(item => item.inStock)
    .reduce((sum, item) => sum + calculateItemPrice(item), 0);
}

function calculateItemPrice(item: Item): number {
  const basePrice = item.price * item.quantity;
  return item.quantity > BULK_DISCOUNT_THRESHOLD
    ? basePrice * BULK_DISCOUNT_RATE
    : basePrice;
}

function finalizeOrder(order: Order, total: number) {
  order.total = total;
  order.status = 'processed';
  db.save(order);
  emailService.sendConfirmation(order);
}

Structured Programming

Dijkstra's rule states that every function should have one entry and one exit: one return statement, no break or continue in loops, and never goto.

In practice, for small functions, multiple returns and early exits improve clarity:

# Guard clauses improve readability in small functions
def calculate_discount(customer):
    if customer is None:
        return 0
    if not customer.is_active:
        return 0
    if customer.total_purchases < MINIMUM_FOR_DISCOUNT:
        return 0

    return customer.total_purchases * DISCOUNT_RATE

Guard clauses handle the error cases at the top of the function, leaving the happy path unindented and clear. This pattern is preferable to deeply nested if-else chains.


DRY: Don't Repeat Yourself

Duplication is the root of all evil in software. Every piece of knowledge should have a single, unambiguous, authoritative representation in the system.

Types of Duplication
Type Example Fix
Exact duplication Same code block in three places Extract to shared function
Structural duplication Same algorithm with different data Template Method or Strategy pattern
Conceptual duplication Same business rule expressed differently Consolidate into single source of truth
Data duplication Same value computed in multiple places Compute once, pass result
The Rule of Three

The first time you write something, just write it. The second time you see duplication, note it. The third time, refactor. This avoids premature abstraction while still catching genuine duplication.


Function Organization Within a Class

The Newspaper Metaphor

Organize functions the way a newspaper organizes articles:

  1. Headline at the top: Public methods (the API) appear first
  2. Synopsis next: High-level private methods called by the public methods
  3. Details last: Low-level helper functions at the bottom

The reader can stop reading at any depth once they have enough understanding.

Vertical Distance

Dependent functions should be close. If one function calls another, they should be vertically close in the source file, and the caller should be above the callee.

// GOOD: Caller above callee, close together
public void processOrder(Order order) {
    validateOrder(order);
    chargeCustomer(order);
    shipOrder(order);
}

private void validateOrder(Order order) {
    // validation logic
}

private void chargeCustomer(Order order) {
    // payment logic
}

private void shipOrder(Order order) {
    // shipping logic
}

Variables should be declared as close to their usage as possible. Local variables at the top of the function. Loop variables inside the loop statement. Instance variables at the top of the class (everyone needs to know they exist).


Common Function Anti-Patterns

Anti-pattern Problem Refactoring
Output arguments appendFooter(report) -- is report input or output? Make it a method: report.appendFooter()
Selector arguments calculate(MONTHLY) enum switches behavior Split: calculateMonthly(), calculateAnnual()
Dead functions Never called, just sitting there Delete them. Version control remembers.
Switch statements Long switches violate SRP and OCP Replace with polymorphism or strategy pattern
Temporal coupling Functions must be called in a specific order Make ordering explicit through return values or builder
Leaky abstraction Function exposes implementation details Hide internals, return domain objects
1# Functions and Methods
2 
3Comprehensive guide to writing small, focused functions that do one thing well. Based on Robert C. Martin's *Clean Code*, Chapters 3 and 4.
4 
5 
6## Table of Contents
71. [The First Rule of Functions](#the-first-rule-of-functions)
82. [Do One Thing](#do-one-thing)
93. [Function Arguments](#function-arguments)
104. [Flag Arguments](#flag-arguments)
115. [Command-Query Separation](#command-query-separation)
126. [Side Effects](#side-effects)
137. [Extract Till You Drop](#extract-till-you-drop)
148. [Structured Programming](#structured-programming)
159. [DRY: Don't Repeat Yourself](#dry-dont-repeat-yourself)
1610. [Function Organization Within a Class](#function-organization-within-a-class)
1711. [Common Function Anti-Patterns](#common-function-anti-patterns)
18 
19---
20 
21## The First Rule of Functions
22 
23**Functions should be small.** The second rule of functions is that they should be smaller than that.
24 
25A well-written function:
26- Fits on one screen (ideally 4-10 lines)
27- Has a name that describes exactly what it does
28- Takes few arguments (zero is best, three is the maximum)
29- Has no side effects
30- Operates at a single level of abstraction
31 
32---
33 
34## Do One Thing
35 
36**Functions should do one thing. They should do it well. They should do it only.**
37 
38### How to Know if a Function Does One Thing
39 
40If you can extract another function from it with a name that is not merely a restatement of its implementation, the function does more than one thing.
41 
42```python
43# BAD: Does three things
44def process_payment(order):
45 # 1. Validate
46 if not order.items:
47 raise ValueError("Empty order")
48 if order.total <= 0:
49 raise ValueError("Invalid total")
50 
51 # 2. Charge
52 payment_result = gateway.charge(order.customer.card, order.total)
53 if not payment_result.success:
54 raise PaymentError(payment_result.error)
55 
56 # 3. Notify
57 email_service.send_receipt(order.customer.email, order)
58 analytics.track("payment_completed", order.id)
59 
60# GOOD: Does one thing, delegates details
61def process_payment(order):
62 validate_order(order)
63 charge_customer(order)
64 send_notifications(order)
65```
66 
67### The Step-Down Rule
68 
69Code should read like a top-down narrative. Every function should be followed by the next level of abstraction.
70 
71```
72To process a payment:
73 We validate the order.
74 We charge the customer.
75 We send notifications.
76 
77To validate the order:
78 We check it has items.
79 We check the total is positive.
80 
81To charge the customer:
82 We call the payment gateway.
83 We handle any payment failure.
84 
85To send notifications:
86 We email the receipt.
87 We track the analytics event.
88```
89 
90This reads like a newspaper: the headline (top-level function) tells you the story, and each successive paragraph (sub-function) provides more detail.
91 
92---
93 
94## Function Arguments
95 
96The ideal number of arguments for a function is zero (niladic). Next comes one (monadic), followed closely by two (dyadic). Three arguments (triadic) should be avoided where possible. More than three (polyadic) requires very special justification.
97 
98### Argument Count Guide
99 
100| Count | Name | When acceptable | Example |
101|-------|------|-----------------|---------|
102| **0** | Niladic | Simple operations | `getCurrentTime()` |
103| **1** | Monadic | Asking a question or transforming input | `isValid(email)`, `parse(json)` |
104| **2** | Dyadic | Natural pairings | `Point(x, y)`, `assertEquals(expected, actual)` |
105| **3** | Triadic | Rarely; consider object | `Color(r, g, b)` |
106| **4+** | Polyadic | Almost never | Wrap in object: `new Config(...)` |
107 
108### Common Monadic Forms
109 
110Three common reasons to pass a single argument:
111 
1121. **Asking a question:** `boolean fileExists(path)` -- returns true/false about the argument
1132. **Transforming it:** `InputStream fileOpen(path)` -- transforms the argument and returns the result
1143. **Event:** `void passwordAttemptFailedNTimes(attempts)` -- uses the argument to alter system state (make this clear from the name)
115 
116### Why Many Arguments Are Problematic
117 
118```java
119// BAD: What does each argument mean? What order?
120createReport(title, startDate, endDate, format, includeCharts, sendEmail, recipients);
121 
122// GOOD: Parameter object groups related arguments
123ReportConfig config = new ReportConfig.Builder()
124 .title("Q4 Revenue")
125 .dateRange(startDate, endDate)
126 .format(PDF)
127 .includeCharts(true)
128 .build();
129createReport(config);
130```
131 
132Each argument increases the difficulty of understanding, testing, and calling the function. Arguments also create ordering dependencies that the reader must memorize.
133 
134---
135 
136## Flag Arguments
137 
138**Flag arguments are ugly.** Passing a boolean into a function loudly declares that the function does two things -- one thing if the flag is true, another if false.
139 
140```python
141# BAD: Flag argument
142def render(document, is_for_print):
143 if is_for_print:
144 # 20 lines of print rendering
145 ...
146 else:
147 # 20 lines of screen rendering
148 ...
149 
150# GOOD: Two clearly named functions
151def render_for_print(document):
152 ...
153 
154def render_for_screen(document):
155 ...
156```
157 
158If a function must behave differently based on a condition, split it into two named functions. If the behaviors share logic, extract the shared part into a private helper.
159 
160---
161 
162## Command-Query Separation
163 
164Functions should either do something (command) or answer something (query), but not both.
165 
166```java
167// BAD: Does this set the attribute or check if it exists?
168if (set("username", "unclebob")) { ... }
169 
170// GOOD: Separate query from command
171if (attributeExists("username")) {
172 setAttribute("username", "unclebob");
173}
174```
175 
176**Commands** change the state of an object. They should return void.
177**Queries** return information about an object. They should not change state.
178 
179When a function both changes state and returns a value, the reader cannot tell from the call site what is happening.
180 
181---
182 
183## Side Effects
184 
185A side effect is when a function promises to do one thing but also does other hidden things.
186 
187### Common Hidden Side Effects
188 
189| Declared purpose | Hidden side effect | Danger |
190|-----------------|-------------------|--------|
191| `checkPassword(user, password)` | Initializes a session | Calling "check" unexpectedly logs user in |
192| `getUser(id)` | Creates user if not found | "Get" implies read-only; caller doesn't expect writes |
193| `toString()` | Modifies internal state | Debugging with print statements changes behavior |
194| `validate(input)` | Sends analytics event | Validation during testing triggers real events |
195 
196### How to Fix Side Effects
197 
1981. **Make the side effect explicit in the name:** `checkPasswordAndInitSession()`
1992. **Separate the concerns:** `checkPassword()` + `initSession()` as two calls
2003. **Prefer option 2** -- separation makes testing easier and keeps functions honest
201 
202---
203 
204## Extract Till You Drop
205 
206If you can extract a named function from a block of code, you should. The extracted function's name adds documentation value, even if the function is only called from one place.
207 
208### When to Extract
209 
210| Signal | Action |
211|--------|--------|
212| A block inside an `if`, `else`, `for`, or `while` | Extract to named function |
213| A comment explaining what the next lines do | Replace comment with named function |
214| A function longer than 10 lines | Look for extraction opportunities |
215| Nested indentation deeper than 2 levels | Extract inner blocks |
216| Code that you'd need to re-read to understand | Name it so you don't have to |
217 
218### Extraction Example
219 
220```typescript
221// BEFORE: Deeply nested, hard to follow
222function processOrders(orders: Order[]) {
223 for (const order of orders) {
224 if (order.status === 'pending') {
225 let total = 0;
226 for (const item of order.items) {
227 if (item.inStock) {
228 total += item.price * item.quantity;
229 if (item.quantity > 10) {
230 total *= 0.9; // bulk discount
231 }
232 }
233 }
234 if (total > 0) {
235 order.total = total;
236 order.status = 'processed';
237 db.save(order);
238 emailService.sendConfirmation(order);
239 }
240 }
241 }
242}
243 
244// AFTER: Each function does one thing
245function processOrders(orders: Order[]) {
246 orders
247 .filter(isPending)
248 .forEach(processOrder);
249}
250 
251function processOrder(order: Order) {
252 const total = calculateOrderTotal(order);
253 if (total > 0) {
254 finalizeOrder(order, total);
255 }
256}
257 
258function calculateOrderTotal(order: Order): number {
259 return order.items
260 .filter(item => item.inStock)
261 .reduce((sum, item) => sum + calculateItemPrice(item), 0);
262}
263 
264function calculateItemPrice(item: Item): number {
265 const basePrice = item.price * item.quantity;
266 return item.quantity > BULK_DISCOUNT_THRESHOLD
267 ? basePrice * BULK_DISCOUNT_RATE
268 : basePrice;
269}
270 
271function finalizeOrder(order: Order, total: number) {
272 order.total = total;
273 order.status = 'processed';
274 db.save(order);
275 emailService.sendConfirmation(order);
276}
277```
278 
279---
280 
281## Structured Programming
282 
283Dijkstra's rule states that every function should have one entry and one exit: one `return` statement, no `break` or `continue` in loops, and never `goto`.
284 
285**In practice, for small functions, multiple returns and early exits improve clarity:**
286 
287```python
288# Guard clauses improve readability in small functions
289def calculate_discount(customer):
290 if customer is None:
291 return 0
292 if not customer.is_active:
293 return 0
294 if customer.total_purchases < MINIMUM_FOR_DISCOUNT:
295 return 0
296 
297 return customer.total_purchases * DISCOUNT_RATE
298```
299 
300Guard clauses handle the error cases at the top of the function, leaving the happy path unindented and clear. This pattern is preferable to deeply nested `if-else` chains.
301 
302---
303 
304## DRY: Don't Repeat Yourself
305 
306Duplication is the root of all evil in software. Every piece of knowledge should have a single, unambiguous, authoritative representation in the system.
307 
308### Types of Duplication
309 
310| Type | Example | Fix |
311|------|---------|-----|
312| **Exact duplication** | Same code block in three places | Extract to shared function |
313| **Structural duplication** | Same algorithm with different data | Template Method or Strategy pattern |
314| **Conceptual duplication** | Same business rule expressed differently | Consolidate into single source of truth |
315| **Data duplication** | Same value computed in multiple places | Compute once, pass result |
316 
317### The Rule of Three
318 
319The first time you write something, just write it. The second time you see duplication, note it. The third time, refactor. This avoids premature abstraction while still catching genuine duplication.
320 
321---
322 
323## Function Organization Within a Class
324 
325### The Newspaper Metaphor
326 
327Organize functions the way a newspaper organizes articles:
328 
3291. **Headline at the top:** Public methods (the API) appear first
3302. **Synopsis next:** High-level private methods called by the public methods
3313. **Details last:** Low-level helper functions at the bottom
332 
333The reader can stop reading at any depth once they have enough understanding.
334 
335### Vertical Distance
336 
337**Dependent functions should be close.** If one function calls another, they should be vertically close in the source file, and the caller should be above the callee.
338 
339```java
340// GOOD: Caller above callee, close together
341public void processOrder(Order order) {
342 validateOrder(order);
343 chargeCustomer(order);
344 shipOrder(order);
345}
346 
347private void validateOrder(Order order) {
348 // validation logic
349}
350 
351private void chargeCustomer(Order order) {
352 // payment logic
353}
354 
355private void shipOrder(Order order) {
356 // shipping logic
357}
358```
359 
360**Variables should be declared as close to their usage as possible.** Local variables at the top of the function. Loop variables inside the loop statement. Instance variables at the top of the class (everyone needs to know they exist).
361 
362---
363 
364## Common Function Anti-Patterns
365 
366| Anti-pattern | Problem | Refactoring |
367|-------------|---------|-------------|
368| **Output arguments** | `appendFooter(report)` -- is `report` input or output? | Make it a method: `report.appendFooter()` |
369| **Selector arguments** | `calculate(MONTHLY)` enum switches behavior | Split: `calculateMonthly()`, `calculateAnnual()` |
370| **Dead functions** | Never called, just sitting there | Delete them. Version control remembers. |
371| **Switch statements** | Long switches violate SRP and OCP | Replace with polymorphism or strategy pattern |
372| **Temporal coupling** | Functions must be called in a specific order | Make ordering explicit through return values or builder |
373| **Leaky abstraction** | Function exposes implementation details | Hide internals, return domain objects |
374 

Discussion