DEV Community

Cover image for Understanding Clean Code: Classes ⚡️
Ali Samir
Ali Samir

Posted on

Understanding Clean Code: Classes ⚡️

Classes are crucial in object-oriented programming but poorly designed ones can lead to problematic code.

Clean Code Chapter 10 highlights the importance of cohesive classes with a single responsibility.

In this article, we'll share key insights and demonstrate their application in JavaScript.


📌 What is Class Cohesion?

Cohesion refers to how closely related the responsibilities of a class are.

A cohesive class focuses on a single purpose and has responsibilities that naturally fit together.

This keeps the class simple, readable, and easy to maintain.

Example: Low Cohesion

class User {
  constructor(name, email) {
    this.name = name;
    this.email = email;
  }

  // Handles user registration
  register() {
    // Registration logic
  }

  // Sends email to user
  sendEmail(message) {
    // Email sending logic
  }

  // Logs activity of the user
  logActivity(activity) {
    console.log(`${this.name} performed: ${activity}`);
  }
}
Enter fullscreen mode Exit fullscreen mode

In the example above, the User class has three unrelated responsibilities: user registration, sending emails, and logging activity.

This class lacks cohesion because it tries to do too many things simultaneously.


📌 The Single Responsibility Principle (SRP)

The Single Responsibility Principle states that a class should have one, and only one, reason to change. This means that each class should focus on a single concern.

If a class has more than one responsibility, changes in one area could break the functionality of another.

⚡️ Refactoring for SRP

Let’s refactor the above example to adhere to SRP:

class User {
  constructor(name, email) {
    this.name = name;
    this.email = email;
  }
}

class UserRegistrationService {
  register(user) {
    // Registration logic
  }
}

class EmailService {
  sendEmail(user, message) {
    // Email sending logic
  }
}

class ActivityLogger {
  logActivity(user, activity) {
    console.log(`${user.name} performed: ${activity}`);
  }
}
Enter fullscreen mode Exit fullscreen mode

Now, each class has a single responsibility:

  • User: represents the user object and holds data.
  • UserRegistrationService: handles user registration.
  • EmailService: handles sending emails.
  • ActivityLogger: handles logging user activity.

With this structure, changes to the email system won't affect the user registration logic, and vice versa.


📌 Benefits of SRP and Cohesion

  • Maintainability: When a class has a single responsibility, it's easier to locate and fix bugs. You don't need to wade through unrelated logic.

  • Scalability: As your project grows, adhering to SRP makes it simpler to add new features. New functionalities can be added in new classes without touching existing ones.

  • Testability: Classes with a single responsibility are easier to test. Each class has a limited scope, so unit tests can focus on individual pieces of functionality.


📌 Identifying Responsibility Overlap

To ensure that your classes are cohesive, look for areas where multiple responsibilities are being combined.

Often, a class starts simple, but as features are added, it can accumulate extra duties.

Example: Refactoring a Payment System

Let’s say we have a PaymentProcessor class that handles multiple tasks:

class PaymentProcessor {
  constructor() {
    this.paymentGateway = new PaymentGateway();
  }

  processPayment(paymentDetails) {
    // Payment processing logic
  }

  validatePaymentDetails(paymentDetails) {
    // Validation logic
  }

  logTransaction(paymentDetails) {
    console.log(`Payment processed: ${JSON.stringify(paymentDetails)}`);
  }
}
Enter fullscreen mode Exit fullscreen mode

Here, PaymentProcessor is responsible for processing payments, validating details, and logging transactions.

This is an opportunity to refactor and split responsibilities:

class PaymentProcessor {
  constructor(paymentGateway, validator, logger) {
    this.paymentGateway = paymentGateway;
    this.validator = validator;
    this.logger = logger;
  }

  processPayment(paymentDetails) {
    if (this.validator.isValid(paymentDetails)) {
      this.paymentGateway.process(paymentDetails);
      this.logger.logTransaction(paymentDetails);
    }
  }
}

class PaymentValidator {
  isValid(paymentDetails) {
    // Validation logic
    return true; // Simplified for example
  }
}

class PaymentLogger {
  logTransaction(paymentDetails) {
    console.log(`Payment processed: ${JSON.stringify(paymentDetails)}`);
  }
}
Enter fullscreen mode Exit fullscreen mode

Now, the PaymentProcessor class has a single responsibility: processing payments.

Validation and logging have been moved to separate classes (PaymentValidator and PaymentLogger).


⚡️ Conclusion

Designing classes with cohesion and adhering to the Single Responsibility Principle ensures your codebase remains flexible, maintainable, and easy to understand.

By splitting responsibilities into separate, focused classes, you reduce the complexity of individual components and make your system more robust.

In JavaScript, where classes are often used in larger frameworks or applications, following these principles will significantly improve the quality of your code.

As the Clean Code book explains, writing clean classes isn’t just about organizing code—it’s about creating a system that can evolve without becoming a nightmare to maintain.


Happy Coding!

Top comments (0)