Clean code: extract until it reads
Turn a long function into named steps you can read from top to bottom.
Watch the narrated video
Every step
Problem
Steps 1–3Refactor
Steps 4–7Payoff
Step 8Recap
Steps 9–10The 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.