Ever looked at a function and felt lost in its complexity? Let's explore a fundamental principle of clean code: functions should maintain only one level of abstraction.
Here's a real-world example of user creation in a web application:
// ❌ A function doing too many things at different abstraction levels
function createUser(userData) {
// Validate data
if (!userData.email || !userData.email.includes('@')) {
return 'Invalid email';
}
if (userData.password.length < 8) {
return 'Password too short';
}
// Hash password
const salt = generateSalt();
const hashedPassword = hashWithSalt(userData.password, salt);
// Format user data
const user = {
email: userData.email.toLowerCase(),
password: hashedPassword,
salt: salt,
createdAt: new Date()
};
// Save to DB
saveUserToDatabase(user);
}
This function mixes different levels of abstraction:
- High-level business logic (user creation flow)
- Mid-level operations (data validation, formatting)
- Low-level details (password hashing)
Let's refactor it following the single level of abstraction principle:
// ✅ Clean version with one level of abstraction
function createUser(userData) {
const validationError = validateUserData(userData);
if (validationError) return validationError;
const securePassword = hashPassword(userData.password);
const formattedUser = formatUserData(userData.email, securePassword);
return saveUserToDatabase(formattedUser);
}
function validateUserData({ email, password }) {
if (!email || !email.includes('@')) return 'Invalid email';
if (password.length < 8) return 'Password too short';
return null;
}
function hashPassword(password) {
const salt = generateSalt();
return {
hash: hashWithSalt(password, salt),
salt
};
}
function formatUserData(email, securePassword) {
return {
email: email.toLowerCase(),
password: securePassword.hash,
salt: securePassword.salt,
createdAt: new Date()
};
}
Benefits of This Approach
- Readability: The main function reads like a story, describing what happens at a high level
- Maintainability: Each function has a single responsibility, making changes safer
- Testability: You can test each piece of logic independently
- Reusability: These focused functions can be reused in other contexts
Key Takeaways
When writing functions:
- Keep them focused on one level of abstraction
- Extract complex operations into well-named functions
- Make the main function tell a story
- Think of each function as a single step in your process
Remember: If you're mixing "how" and "what" in the same function, you're probably dealing with multiple levels of abstraction. Split them up!
Top comments (0)