DEV Community

Daniil Kornilov
Daniil Kornilov

Posted on Edited on

When clean-code rules need context

Disclosure: This article was created or edited with AI assistance. Revised on 13 October 2026 to remove promotional links and unsupported outcome claims. It is educational guidance, not a verified personal case study or a measured result. Examples are illustrative; use only your own documented experience and measurements.

The sections below present options and illustrative examples. Adapt them to the requirements of your project or role; they do not establish a universal rule or promise a result.

The Problem With Small Functions

Clean Code says: "Functions should do one thing. They should do it well. They should do it only."

Sounds great in theory. In practice, it creates this:

// "Clean" version - 8 functions across 3 files
async function processOrder(order) {
  validateOrder(order);
  const total = calculateTotal(order);
  const discount = applyDiscount(total, order.coupon);
  const tax = calculateTax(discount, order.region);
  const finalAmount = addTax(discount, tax);
  chargeCustomer(order.customer, finalAmount);
  sendConfirmation(order.customer, order);
  updateInventory(order.items);
}

// What you actually need to understand: 8 function signatures,
// 3 files, and constant jumping between them
Enter fullscreen mode Exit fullscreen mode
// "Dirty" version - 1 function, 25 lines
async function processOrder(order) {
  if (!order.items.length) throw new Error('Empty order');

  let total = order.items.reduce((sum, item) => sum + item.price * item.qty, 0);

  if (order.coupon) {
    const discount = coupons[order.coupon];
    if (discount) total *= (1 - discount);
  }

  const tax = total * TAX_RATES[order.region];
  const finalAmount = total + tax;

  await stripe.charge(order.customer.paymentId, finalAmount);
  await mailer.send(order.customer.email, 'order-confirmation', { order });
  await db.inventory.decrementMany(order.items);
}
Enter fullscreen mode Exit fullscreen mode

The second version keeps the flow together, which may be easier to read in this example. You can understand the entire flow without jumping between files. Every line tells you exactly what happens and in what order.

The Naming Obsession

Clean Code: "Variable names should be meaningful and descriptive."

The result:

// Over-engineered naming
const numberOfItemsInShoppingCart = cart.length;
const isUserCurrentlyLoggedInToTheApplication = !!session.user;
const listOfProductsThatAreCurrentlyOnSale = products.filter(p => p.onSale);

// Just... normal naming
const itemCount = cart.length;
const isLoggedIn = !!session.user;
const saleProducts = products.filter(p => p.onSale);
Enter fullscreen mode Exit fullscreen mode

Context matters. Inside a CartComponent, everyone knows what items means. You don't need cartItemsForCurrentUserSession.

The Abstraction Addiction

This is the big one. Treating abstraction or DRY as unconditional rules can obscure the trade-offs.

But here's what actually happens:

Day 1: Two functions share 5 lines of code.
Day 2: You extract a shared function. Feels good.
Day 30: The two callers now need slightly different behavior.
Day 31: You add a parameter flag. doThing(data, { legacy: true })
Day 60: Three more callers, three more flags.
Day 90: The "shared" function has 8 parameters, 4 flags, and nobody understands it.

This is called the wrong abstraction, and it's worse than duplication.

// The "clean" abstraction that grew into a monster
function formatData(data, {
  includeTimestamp = false,
  useLocalTime = false,
  format = 'json',
  includeMetadata = false,
  stripNulls = true,
  flattenNested = false,
  customTransform = null,
  legacyMode = false
} = {}) {
  // 200 lines of branching logic
}

// vs. just writing what you need
function formatUserForAPI(user) { /* 10 lines */ }
function formatOrderForEmail(order) { /* 12 lines */ }
function formatLogEntry(entry) { /* 8 lines */ }
Enter fullscreen mode Exit fullscreen mode

Duplication is far cheaper than the wrong abstraction.

1. Optimize for Reading, Not Writing

Optimize for a future reader who lacks today's context.

This means: inline over abstraction, colocate over separate, explicit over clever.

2. The Rule of Three

Waiting for several concrete uses is a heuristic, not a requirement. Extract earlier when a clear contract or a necessary boundary justifies it.

3. Functions Can Be 30 Lines

A 30-line function that tells a clear story is better than ten 3-line functions you have to mentally stitch together.

4. Delete Aggressively

The best refactoring is deletion. If nobody uses it, it shouldn't exist.

The Real "Clean Code"

Code is clean when:

  • A new team member can understand it quickly
  • You can change it without breaking unrelated things
  • It does what it says it does
  • There's not more of it than necessary

Notice what's NOT on this list: small functions, maximum abstraction, zero duplication, design patterns everywhere.

Clean code isn't about following rules. It's about empathy for the next reader.

The order-processing snippets illustrate code organization. Service names are placeholders, not a payment-provider integration; a real checkout needs an explicit failure, transaction and retry contract.

Top comments (0)