DEV Community

56kode
56kode

Posted on

One Level of Abstraction: The Key to Clean Functions

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);
}
Enter fullscreen mode Exit fullscreen mode

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()
  };
}
Enter fullscreen mode Exit fullscreen mode

Benefits of This Approach

  1. Readability: The main function reads like a story, describing what happens at a high level
  2. Maintainability: Each function has a single responsibility, making changes safer
  3. Testability: You can test each piece of logic independently
  4. 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)