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
Top comments (0)