# Long Method

> A function that does several jobs in a row and has grown too long to read, name or test as one unit.

- kind: smell
- category: bloaters
- aliases: God Function
- canonical: https://aipatterns.dev/refactoring/smells/long-method
- inspired-by: https://refactoring.guru/smells/long-method

## Signs

- You have to scroll to see the whole function, or you need to keep more than a handful of local variables in your head at once.
- Blank lines and comments split the body into "paragraphs" such as `// validate`, `// compute totals`, `// send email`.
- The name is vague (`process`, `handle`, `run`) because no specific name covers everything it does.
- Tests for it need large fixtures, and each test asserts on many unrelated outputs.
- Diffs touch the same function for unrelated reasons: a tax change and a logging change land in the same 150 lines.

## Why it happens

Adding a line to an existing function always feels cheaper than creating a new one. Each change is small and reasonable, so nobody notices the function crossing the line from "a step" to "a workflow". Local variables make it worse: once five temporaries are shared across the body, pulling out a piece means deciding what to pass in and out, so people keep appending instead.

## Example

An order-placement handler that validates, prices, persists and notifies in one block.

```ts title="placeOrder.ts"
async function placeOrder(req: OrderRequest, db: Db, mailer: Mailer): Promise<string> {
  // validate
  if (!req.items.length) throw new Error("empty order");
  for (const item of req.items) {
    if (item.qty <= 0) throw new Error(`bad qty for ${item.sku}`);
  }

  // price
  let subtotal = 0;
  for (const item of req.items) {
    const product = await db.products.get(item.sku);
    subtotal += product.priceCents * item.qty;
  }
  let discount = 0;
  if (req.coupon === "SPRING10") discount = Math.round(subtotal * 0.1);
  const tax = Math.round((subtotal - discount) * 0.08);
  const total = subtotal - discount + tax;

  // persist
  const id = await db.orders.insert({ ...req, subtotal, discount, tax, total });

  // notify
  await mailer.send(req.email, "Order received", `Order ${id}: $${(total / 100).toFixed(2)}`);
  return id;
}
```

After extracting each paragraph into a named function, the top-level function reads as a table of contents.

```ts title="placeOrder.ts"
async function placeOrder(req: OrderRequest, db: Db, mailer: Mailer): Promise<string> {
  assertValid(req);
  const price = await priceOrder(req, db.products);
  const id = await db.orders.insert({ ...req, ...price });
  await sendConfirmation(mailer, req.email, id, price.total);
  return id;
}

function assertValid(req: OrderRequest): void {
  if (!req.items.length) throw new Error("empty order");
  const bad = req.items.find((i) => i.qty <= 0);
  if (bad) throw new Error(`bad qty for ${bad.sku}`);
}

async function priceOrder(req: OrderRequest, products: ProductRepo): Promise<Price> {
  const subtotal = await subtotalOf(req.items, products);
  const discount = couponDiscount(req.coupon, subtotal);
  const tax = Math.round((subtotal - discount) * TAX_RATE);
  return { subtotal, discount, tax, total: subtotal - discount + tax };
}
```

## Treatment

- If the body has comment-headed paragraphs, use [Extract Method](https://aipatterns.dev/refactoring/techniques/extract-method.md) on each one and name it after the comment.
- If temporaries block extraction, first apply [Replace Temp with Query](https://aipatterns.dev/refactoring/techniques/replace-temp-with-query.md) so the extracted code can recompute the value.
- If many parameters must flow into the extracted pieces, group them with [Introduce Parameter Object](https://aipatterns.dev/refactoring/techniques/introduce-parameter-object.md) or [Preserve Whole Object](https://aipatterns.dev/refactoring/techniques/preserve-whole-object.md).
- If locals are so entangled that no clean cut exists, use [Replace Method with Method Object](https://aipatterns.dev/refactoring/techniques/replace-method-with-method-object.md): the locals become fields and every step becomes a method.
- If conditionals dominate, apply [Decompose Conditional](https://aipatterns.dev/refactoring/techniques/decompose-conditional.md) to give each condition and branch a name.
- If a loop does one self-contained job, extract the loop into its own function.

## Payoff

- Each function has one name that tells the truth, so callers and reviewers read intent, not mechanics.
- Small functions are testable in isolation and reusable elsewhere.
- Duplicate fragments become visible once pieces have names.
- Changes land in one small function, so diffs and merge conflicts shrink.

## When to ignore

- The function is long but flat and linear, such as a configuration table, a route list or a generated mapping. Splitting it adds jumps without adding meaning.
- A hot loop where a profiler shows that call overhead matters and the compiler does not inline. This is rare; measure first.
- Test functions that set up one scenario end to end. Arrange-act-assert in one place can be clearer than helper soup.

## Agent checklist

- [ ] Tests cover the function before you split it; add characterization tests if they don't.
- [ ] Each extracted function has a name specific enough that the comment above the old paragraph can be deleted.
- [ ] Extracted functions take the data they need as arguments and do not reach back into the caller's locals through closures or shared mutable state.
- [ ] The original function now reads as a short sequence of steps at one level of abstraction.
- [ ] Behavior is unchanged: the same tests pass with no edits to their assertions.
