Most pull requests are slow to review because they’re hard to read, not because they’re wrong. Clean structure beats clever logic every time. Here are 6 refactoring patterns that make your code instantly reviewable.
1. Replace Generic Variable Names With Domain Names
If a reviewer has to scroll up to understand a variable, the name failed.
Before
const filtered = users.filter(u => u.active);
const mapped = filtered.map(u => u.email);
After
const activeUsers = users.filter(user => user.active);
const activeUserEmails = activeUsers.map(user => user.email);
Now the reader never needs to inspect the implementation. You removed one mental lookup per line, which compounds across large diffs.
2. Convert Boolean Flags Into Explicit Types
Boolean arguments hide intent at the call site. Replace them with union types.
Before
function createUser(name: string, isAdmin: boolean) {
// ...
}
createUser("Zamir", true);
After
type Role = "admin" | "editor" | "viewer";
function createUser(name: string, role: Role) {
// ...
}
createUser("Zamir", "admin");
The call becomes self-documenting. Reviewers don’t need to jump to the definition to understand what true means.
3. Split Multi-Responsibility Functions Into Pipelines
Long functions are the #1 reason PRs get stuck. Break them into named steps.
Before
async function checkout(cart: Cart, user: User) {
if (cart.items.length === 0) throw new Error("Empty");
let total = 0;
for (const item of cart.items) {
total += item.price * item.quantity;
}
const payment = await stripe.charge({ total, user });
await saveOrder(user.id, cart.items, total);
return payment.id;
}
After
async function checkout(cart: Cart, user: User) {
validateCart(cart);
const total = calculateTotal(cart);
const payment = await processPayment(total, user);
await persistOrder(user, cart, total, payment.id);
return payment.id;
}
function validateCart(cart: Cart) {
if (cart.items.length === 0) throw new Error("Empty");
}
function calculateTotal(cart: Cart) {
return cart.items.reduce((sum, i) => sum + i.price * i.quantity, 0);
}
Now the top-level function reads like a sequence diagram. Review time drops because understanding flow takes seconds, not minutes.
This pattern becomes critical when dealing with larger systems like those described in the JavaScript application architecture in 2026 and why system design is the one skill AI cannot automate, where readability directly impacts team velocity.
4. Flatten Nested Logic With Early Returns
Nested conditions hide the happy path. Flatten them.
Before
async function updateUser(id: string, data: Update) {
const user = await findUser(id);
if (user) {
if (user.active) {
if (data.email) {
await saveUser(id, data);
return { ok: true };
} else {
throw new Error("Email required");
}
} else {
throw new Error("Inactive");
}
} else {
throw new Error("Not found");
}
}
After
async function updateUser(id: string, data: Update) {
const user = await findUser(id);
if (!user) throw new Error("Not found");
if (!user.active) throw new Error("Inactive");
if (!data.email) throw new Error("Email required");
await saveUser(id, data);
return { ok: true };
}
Same logic, half the cognitive load. Reviewers can validate all edge cases in a linear scan.
5. Replace Magic Numbers With Named Constants
Numbers without context force guesswork.
Before
if (password.length < 8) throw new Error("Too short");
setTimeout(refreshToken, 3600000);
After
const MIN_PASSWORD_LENGTH = 8;
const TOKEN_REFRESH_INTERVAL_MS = 60 * 60 * 1000;
if (password.length < MIN_PASSWORD_LENGTH) {
throw new Error(`Min length ${MIN_PASSWORD_LENGTH}`);
}
setTimeout(refreshToken, TOKEN_REFRESH_INTERVAL_MS);
Now the code explains itself. Changing behavior becomes a one-line edit instead of a risky search.
6. Extract Pure Functions From Side Effects
Business logic should be testable without mocks.
Before
async function calculatePrice(productId: string) {
const product = await db.products.find(productId);
const now = new Date();
if (now.getMonth() === 11) {
return product.price * 0.8;
}
return product.price;
}
After
async function calculatePrice(productId: string) {
const product = await db.products.find(productId);
const isHoliday = new Date().getMonth() === 11;
return computePrice(product.price, isHoliday);
}
function computePrice(base: number, isHoliday: boolean) {
if (isHoliday) return base * 0.8;
return base;
}
computePrice is now deterministic and testable in isolation. The outer function handles I/O. This separation is what makes large systems maintainable.
Clean code is not about style. It is about reducing the time another developer needs to understand your intent. Take one function in your codebase today, apply these patterns, and measure how long it takes to review before and after.
Top comments (0)