JavaScript Clean Code Mastery: Part 2 - Functions That Do One Thing Well
sizan mahmud0

sizan mahmud0 @sizan_mahmud0_e7c3fd0cb68

About: Full-Stack Developer | ⚡ Django & Laravel | 🖥️ Vue.js, Next.js & React | 🐳 Docker | 🔄 Celery & Redis | Passionate about building scalable apps & solving real-world problems 🚀

Joined:
Sep 25, 2025

JavaScript Clean Code Mastery: Part 2 - Functions That Do One Thing Well

Publish Date: Nov 22 '25
2 0

Welcome Back, Code Cleaners!

In Part 1, we mastered meaningful variable names and killed the var keyword forever. Today, we're tackling the heart of clean code: functions.

I once wrote a 250-line function called processUserData(). It validated, transformed, saved to database, sent emails, logged activities, and made coffee (okay, not that last one). When a bug appeared, I spent 4 hours finding it in that monster function.

Never again.

Today's Mission:

  • Write small functions that do ONE thing
  • Master arrow functions (and avoid common mistakes)
  • Name functions like a pro
  • Make your code self-documenting

Let's transform those god functions into clean, testable masterpieces.


Practice 1: Write Small Functions That Do One Thing

The Golden Rule: If you can't describe what a function does in one sentence without using "and", it's doing too much.

❌ Bad: God Function

function processUserData(user) {
  // Validate
  if (!user.email.includes('@')) {
    throw new Error('Invalid email');
  }
  if (user.age < 18) {
    throw new Error('User too young');
  }

  // Transform
  user.name = user.name.trim().toLowerCase();
  user.email = user.email.toLowerCase();

  // Save to database
  const query = `INSERT INTO users (name, email, age) VALUES ('${user.name}', '${user.email}', ${user.age})`;
  db.execute(query);

  // Send welcome email
  const subject = 'Welcome!';
  const body = `Hello ${user.name}, welcome to our platform!`;
  emailService.send(user.email, subject, body);

  // Log activity
  logger.info(`User ${user.name} registered at ${new Date()}`);

  return user;
}
Enter fullscreen mode Exit fullscreen mode

What's wrong?

  • Does 5 different things (validate, transform, save, email, log)
  • Impossible to test one piece without triggering all others
  • Can't reuse validation elsewhere
  • 25 lines of tangled logic

✅ Good: Single Responsibility Functions

function validateUserEmail(email) {
  if (!email.includes('@')) {
    throw new Error('Invalid email');
  }
}

function validateUserAge(age) {
  if (age < 18) {
    throw new Error('User must be 18 or older');
  }
}

function normalizeUserData(user) {
  return {
    ...user,
    name: user.name.trim().toLowerCase(),
    email: user.email.toLowerCase()
  };
}

function saveUserToDatabase(user) {
  const query = 'INSERT INTO users (name, email, age) VALUES (?, ?, ?)';
  return db.execute(query, [user.name, user.email, user.age]);
}

function sendWelcomeEmail(user) {
  const subject = 'Welcome!';
  const body = `Hello ${user.name}, welcome to our platform!`;
  return emailService.send(user.email, subject, body);
}

function logUserRegistration(user) {
  logger.info(`User ${user.name} registered at ${new Date()}`);
}

// Compose small functions into a workflow
async function registerUser(user) {
  validateUserEmail(user.email);
  validateUserAge(user.age);

  const normalizedUser = normalizeUserData(user);

  await saveUserToDatabase(normalizedUser);
  await sendWelcomeEmail(normalizedUser);

  logUserRegistration(normalizedUser);

  return normalizedUser;
}
Enter fullscreen mode Exit fullscreen mode

Benefits:

  • ✅ Each function is 3-5 lines (easy to understand)
  • ✅ Easy to test (test validateUserEmail independently)
  • ✅ Reusable (use validateUserEmail in 10 different places)
  • ✅ Easy to modify (change email logic without touching registration)
  • ✅ Names explain what happens (no comments needed!)

The 20-Line Rule: If your function exceeds 20 lines, it's probably doing too much. Break it down.


Practice 2: Function Names Should Be Verbs

Functions DO things. Names should reflect actions.

❌ Bad: Noun-Based Names

function user(id) {
  return db.query('SELECT * FROM users WHERE id = ?', [id]);
}

function email(user, subject, body) {
  emailService.send(user.email, subject, body);
}

function validation(data) {
  return data.age > 18;
}
Enter fullscreen mode Exit fullscreen mode

✅ Good: Verb-Based Names

function getUser(id) {
  return db.query('SELECT * FROM users WHERE id = ?', [id]);
}

function sendEmail(user, subject, body) {
  emailService.send(user.email, subject, body);
}

function validateAge(age) {
  return age >= 18;
}
Enter fullscreen mode Exit fullscreen mode

Common Verb Prefixes:

  • get - Retrieve data: getUser(), getOrders()
  • set - Modify data: setUsername(), setTheme()
  • create - Make new: createUser(), createOrder()
  • update - Change existing: updateProfile(), updateStatus()
  • delete - Remove: deleteAccount(), deletePost()
  • validate - Check validity: validateEmail(), validatePassword()
  • calculate - Compute: calculateTotal(), calculateTax()
  • format - Transform: formatDate(), formatCurrency()
  • is/has/can - Return boolean: isValid(), hasPermission(), canEdit()

Practice 3: Use Arrow Functions (But Know When NOT To)

Arrow functions are clean, concise, and solve the dreaded this binding issue. But they're not always the right choice.

✅ Good: When to Use Arrow Functions

1. Array Methods

const numbers = [1, 2, 3, 4, 5];

// ❌ Traditional function - verbose
const doubled = numbers.map(function(number) {
  return number * 2;
});

// ✅ Arrow function - clean
const doubled = numbers.map(number => number * 2);

// ✅ Multiple operations
const result = numbers
  .filter(num => num > 2)
  .map(num => num * 2)
  .reduce((sum, num) => sum + num, 0);
Enter fullscreen mode Exit fullscreen mode

2. Callbacks

// ❌ Traditional function
setTimeout(function() {
  console.log('Delayed');
}, 1000);

// ✅ Arrow function
setTimeout(() => {
  console.log('Delayed');
}, 1000);
Enter fullscreen mode Exit fullscreen mode

3. Promise Chains

// ✅ Clean promise chain
fetchUser(userId)
  .then(user => fetchOrders(user.id))
  .then(orders => processOrders(orders))
  .catch(error => handleError(error));
Enter fullscreen mode Exit fullscreen mode

4. Lexical this Binding

// ❌ Traditional function - 'this' is undefined
const person = {
  name: 'Alice',
  greet: function() {
    setTimeout(function() {
      console.log('Hello, ' + this.name);  // this is undefined!
    }, 1000);
  }
};

// ✅ Arrow function - inherits 'this'
const person = {
  name: 'Alice',
  greet: function() {
    setTimeout(() => {
      console.log(`Hello, ${this.name}`);  // works!
    }, 1000);
  }
};
Enter fullscreen mode Exit fullscreen mode

❌ Bad: When NOT to Use Arrow Functions

1. Object Methods

// ❌ Arrow function as method - 'this' won't work
const user = {
  name: 'Alice',
  greet: () => {
    console.log(`Hello, ${this.name}`);  // this is undefined!
  }
};

// ✅ Regular function
const user = {
  name: 'Alice',
  greet() {
    console.log(`Hello, ${this.name}`);  // works!
  }
};
Enter fullscreen mode Exit fullscreen mode

2. Constructors

// ❌ Can't use arrow functions as constructors
const Person = (name) => {
  this.name = name;
};
const alice = new Person('Alice');  // TypeError!

// ✅ Regular function or class
class Person {
  constructor(name) {
    this.name = name;
  }
}
Enter fullscreen mode Exit fullscreen mode

3. Methods That Need arguments

// ❌ Arrow functions don't have 'arguments'
const sum = () => {
  return Array.from(arguments).reduce((a, b) => a + b);  // ReferenceError!
};

// ✅ Regular function or rest parameters
function sum(...numbers) {
  return numbers.reduce((a, b) => a + b, 0);
}
Enter fullscreen mode Exit fullscreen mode

Practice 4: Keep Function Parameters to 3 or Fewer

The Problem: Too many parameters are hard to remember and error-prone.

❌ Bad: Many Parameters

function createUser(name, email, age, address, phone, country, city, zipCode) {
  // Which order again? Easy to mess up!
  return { name, email, age, address, phone, country, city, zipCode };
}

// Caller has to remember exact order
createUser('Alice', 'alice@example.com', 25, '123 Main St', '555-0100', 'USA', 'NYC', '10001');
Enter fullscreen mode Exit fullscreen mode

✅ Good: Use Object Parameter

function createUser({ name, email, age, address, phone, country, city, zipCode }) {
  return { name, email, age, address, phone, country, city, zipCode };
}

// Caller uses named properties - order doesn't matter!
createUser({
  name: 'Alice',
  email: 'alice@example.com',
  age: 25,
  city: 'NYC',
  country: 'USA',
  address: '123 Main St',
  phone: '555-0100',
  zipCode: '10001'
});

// Even better: With defaults
function createUser({ 
  name, 
  email, 
  age = 18,  // Default age
  country = 'USA'  // Default country
}) {
  return { name, email, age, country };
}

createUser({ name: 'Alice', email: 'alice@example.com' });
// Returns: { name: 'Alice', email: 'alice@example.com', age: 18, country: 'USA' }
Enter fullscreen mode Exit fullscreen mode

Benefits:

  • Self-documenting (clear what each parameter is)
  • Order-independent (can't mess up parameter order)
  • Easy to add optional parameters
  • Can provide defaults easily

Practice 5: Don't Use Flags as Function Parameters

The Problem: Boolean flags indicate a function doing multiple things.

❌ Bad: Boolean Flags

function createUser(name, email, isAdmin) {
  const user = { name, email };

  if (isAdmin) {
    user.role = 'admin';
    user.permissions = ['read', 'write', 'delete'];
    sendAdminWelcomeEmail(user);
  } else {
    user.role = 'user';
    user.permissions = ['read'];
    sendUserWelcomeEmail(user);
  }

  return user;
}

// What does 'true' mean here? Have to check function definition!
createUser('Alice', 'alice@example.com', true);
Enter fullscreen mode Exit fullscreen mode

✅ Good: Separate Functions

function createRegularUser(name, email) {
  const user = {
    name,
    email,
    role: 'user',
    permissions: ['read']
  };

  sendUserWelcomeEmail(user);
  return user;
}

function createAdminUser(name, email) {
  const user = {
    name,
    email,
    role: 'admin',
    permissions: ['read', 'write', 'delete']
  };

  sendAdminWelcomeEmail(user);
  return user;
}

// Clear what's happening!
createRegularUser('Alice', 'alice@example.com');
createAdminUser('Bob', 'bob@example.com');
Enter fullscreen mode Exit fullscreen mode

Real-World Example: Refactoring a Checkout Function

Before: Messy

function checkout(cart, user, paymentMethod, shippingAddress, billingAddress, coupon) {
  // Validate cart
  if (cart.items.length === 0) throw new Error('Cart is empty');

  // Calculate totals
  let subtotal = 0;
  for (let i = 0; i < cart.items.length; i++) {
    subtotal += cart.items[i].price * cart.items[i].quantity;
  }

  // Apply coupon
  let discount = 0;
  if (coupon) {
    discount = coupon.type === 'percent' ? subtotal * (coupon.value / 100) : coupon.value;
  }

  // Calculate tax
  const tax = (subtotal - discount) * 0.08;

  // Calculate shipping
  let shipping = subtotal > 100 ? 0 : 10;

  const total = subtotal - discount + tax + shipping;

  // Process payment
  const payment = processPayment(paymentMethod, total);
  if (!payment.success) throw new Error('Payment failed');

  // Create order
  const order = {
    user: user.id,
    items: cart.items,
    subtotal,
    discount,
    tax,
    shipping,
    total,
    shippingAddress,
    billingAddress
  };

  // Save and send email
  saveOrder(order);
  sendOrderConfirmation(user.email, order);

  return order;
}
Enter fullscreen mode Exit fullscreen mode

After: Clean

function calculateSubtotal(items) {
  return items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
}

function calculateDiscount(subtotal, coupon) {
  if (!coupon) return 0;

  return coupon.type === 'percent' 
    ? subtotal * (coupon.value / 100) 
    : coupon.value;
}

function calculateTax(amount, taxRate = 0.08) {
  return amount * taxRate;
}

function calculateShipping(subtotal, freeShippingThreshold = 100) {
  return subtotal >= freeShippingThreshold ? 0 : 10;
}

function validateCart(cart) {
  if (cart.items.length === 0) {
    throw new Error('Cart is empty');
  }
}

async function checkout({ cart, user, paymentMethod, shippingAddress, billingAddress, coupon }) {
  // Validate
  validateCart(cart);

  // Calculate amounts
  const subtotal = calculateSubtotal(cart.items);
  const discount = calculateDiscount(subtotal, coupon);
  const taxableAmount = subtotal - discount;
  const tax = calculateTax(taxableAmount);
  const shipping = calculateShipping(subtotal);
  const total = taxableAmount + tax + shipping;

  // Process payment
  const payment = await processPayment(paymentMethod, total);
  if (!payment.success) {
    throw new Error('Payment failed');
  }

  // Create and save order
  const order = {
    userId: user.id,
    items: cart.items,
    amounts: { subtotal, discount, tax, shipping, total },
    shippingAddress,
    billingAddress
  };

  await saveOrder(order);
  await sendOrderConfirmation(user.email, order);

  return order;
}
Enter fullscreen mode Exit fullscreen mode

Improvements:

  • ✅ Each calculation is a separate function (testable!)
  • ✅ Used object parameter (clear what's passed)
  • ✅ Function names explain what they do
  • ✅ Main function reads like a story
  • ✅ Easy to modify any calculation independently

Quick Wins Checklist for Part 2

Audit your functions with these questions:

Can I describe each function in one sentence without "and"?
Is each function under 20 lines?
Do function names start with verbs?
Am I using arrow functions for callbacks/array methods?
Do I have more than 3 function parameters? (Use object instead)
Do I have boolean flags as parameters? (Split into separate functions)


Part 2 Conclusion: Functions Are the Building Blocks

Great functions are like LEGO blocks:

  • Small and focused
  • Easy to understand
  • Can be combined in infinite ways
  • Reusable everywhere

When you write a 250-line god function, you're building with concrete blocks. Heavy, inflexible, hard to modify.

When you write 10-line focused functions, you're building with LEGOs. Light, flexible, infinitely reconfigurable.

Challenge for Today: Find your longest function. Break it into 3-5 smaller functions. Share the before/after line count in the comments! 📊


Coming Up in Part 3: Modern JavaScript Features 🚀

Next time, we'll explore the modern JavaScript features that make your code cleaner and more expressive:

  • Destructuring - Unpack objects and arrays cleanly
  • Template Literals - Say goodbye to string concatenation hell
  • Optional Chaining - Stop defensive null checking
  • Nullish Coalescing - Better default values
  • Spread Operator - Copy and merge like a pro

These features will cut your code by 30-40% while making it more readable. Don't miss it!


Ready to write better functions? 👏 Clap for clean functions! (50 claps available)

Get notified of Part 3! 🔔 Follow me - Part 3 drops in 3 days!

What's your longest function? 💬 Drop the line count in the comments - let's see who has the biggest monster to refactor! 😄

Sharing is caring! 📤 Share this with your dev team - everyone benefits from cleaner functions.


This is Part 2 of the 7-part "JavaScript Clean Code Mastery" series.

← Part 1: Naming & Variables | Part 3: Modern JavaScript Features

Tags: #JavaScript #CleanCode #Functions #ArrowFunctions #WebDevelopment #Programming #ES6 #SoftwareEngineering #Tutorial

Comments 0 total

    Add comment