I'll Be Backend
Principles · TypeScript

Clean code: extract until it reads

Turn a long function into named steps you can read from top to bottom.

10 steps4 chapters~4 min at 1.25×#extract method#long method#readability#split variable
The lesson plays right here. Pause anywhere, step back, or jump to a step below. Watch the TypeScript video on YouTube Code on GitHub

Watch the narrated video

Every step

Problem

Steps 1–3

Refactor

Steps 4–7

Payoff

Step 8

Recap

Steps 9–10

The code: before and after

Before

function confirmOrder(order: Order): string {
  if (order.items.length === 0) {
    throw new Error("No items");
  }
  let total = 0;
  for (const item of order.items) {
    total += item.price * item.qty;
  }
  total += total < 50 ? 4.99 : 0;
  total += order.express ? 7.5 : 0;
  const count = order.items.length;
  return `${count} items, $${total.toFixed(2)}`;
}

After

function confirmOrder(order: Order): string {
  requireItems(order);
  const goods = subtotal(order);
  const discount = memberDiscount(order, goods);
  const total = goods - discount + deliveryFee(order, goods);
  const count = order.items.length;
  return `${count} items, $${total.toFixed(2)}`;
}

function requireItems(order: Order) {
  if (order.items.length === 0) {
    throw new Error("No items");
  }
}

function subtotal(order: Order) {
  let sum = 0;
  for (const item of order.items) {
    sum += item.price * item.qty;
  }
  return sum;
}

function memberDiscount(order: Order, goods: number) {
  return order.member ? goods * 0.1 : 0;
}

function deliveryFee(order: Order, goods: number) {
  let fee = 0;
  fee += goods < 50 ? 4.99 : 0;
  fee += order.express ? 7.5 : 0;
  return fee;
}

Transcript, step by step

Problem

Step 1: A checkout that works Before

This function confirms a grocery order and returns the line the customer sees. It checks there are items, adds up the prices, adds delivery, and builds the message. Delivery is free from fifty dollars, express adds seven fifty, and the tests pass.

Step 2: A discount in the wrong place Code smell

Members now get ten percent off their groceries. Someone adds the discount near the end, because total looks like the number to change. By that line, total already includes delivery, so members get ten percent off the delivery fees too, and nobody asked for that. This is a long function, one body doing several jobs, so you have to read every line to find the one you need. It checks the order here, adds up the goods here, adds delivery here, and builds the message here. Total means the goods at the top and the goods plus fees at the bottom, and only reading every line tells you which one you've got.

Step 3: Undo, then extract Plan

Let's back that change out. Extract Function takes a few lines that do one job and moves them into a function named after that job. We'll keep extracting until confirm order reads like a list of steps, and then the discount should have an obvious place.

Refactor

Step 4: Pull out the guard Extract Function

The first lines guard against an empty order, so they move into a function called require items. Confirm order now opens with one line that says what it checks, and you only read the error handling when you care about it.

Step 5: Pull out the subtotal Extract Function

The loop adds up what the groceries cost, so it becomes a function called subtotal. Inside it, the running number gets its own name, sum, so it can't be mixed up with the total in confirm order. Back in confirm order, total now starts from the subtotal in a single line.

Step 6: One name per meaning Split Variable

Total was doing two jobs, holding the goods first and the amount to pay after that. So the goods get a variable of their own, and total starts from it. The free delivery check now reads the goods, which is what it meant all along.

Step 7: Pull out delivery Extract Function

The two delivery lines move into a function called delivery fee, and it takes the goods to check the free delivery limit. Total is now the goods plus the delivery fee, and you can read that straight off the line. Read confirm order from the top and you get the steps in order, because it requires items, adds up the goods, adds the fee, and returns the message. The message line already says what it does, so it stays where it is, and that's the point where you stop extracting.

Payoff

Step 8: The discount, in the right place Payoff

Now the discount comes back, as a function called member discount that takes ten percent off the goods. In confirm order, it comes off the goods and the delivery fee is added after, so members only save on what they buy. The jobs that were tangled together now sit apart, with the check here, the goods here, delivery here, and the message here. Require items, subtotal and delivery fee didn't change, so the fees can't pick up the discount by accident.

Recap

Step 9: Where we started, where we ended Before / after

On the left, one function checked the order, added up the prices, charged delivery and built the message. Total changed meaning halfway down, and that's why the discount landed on the fees. On the right, confirm order reads as the steps of a checkout. Each step it calls is a small function below, named for its job.

Step 10: When to reach for it Takeaways

Extract a function when you have to read all of one to find the part you need to change. Leave lines alone when they already say what they do, because a function named after them would only repeat them. You'll spot the need in long functions, in comments that label each section, and in one variable that changes meaning on the way down.