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 functio…
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;
}
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;
}
Benefits:
- ✅ Each function is 3-5 lines (easy to understand)
- ✅ Easy to test (test
validateUserEmailindependently) - ✅ Reusable (use
validateUserEmailin 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;
}
✅ 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;
}
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);
2. Callbacks
// ❌ Traditional function
setTimeout(function() {
console.log('Delayed');
}, 1000);
// ✅ Arrow function
setTimeout(() => {
console.log('Delayed');
}, 1000);
3. Promise Chains
// ✅ Clean promise chain
fetchUser(userId)
.then(user => fetchOrders(user.id))
.then(orders => processOrders(orders))
.catch(error => handleError(error));
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);
}
};
❌ 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!
}
};
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;
}
}
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);
}
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');
✅ 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' }
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);
✅ 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');
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;
}
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;
}
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