Blog Datasheets Home About me Clients My work Services Contact

G2Labs Grzegorz Grzęda

Best practices for writing clean and SOLID JavaScript code

May 25, 2023

Best Practices for Writing Clean and SOLID JavaScript Code

As a developer, writing clean and maintainable code is crucial for the success of any software project. In the world of JavaScript, where the language is highly dynamic and flexible, it’s important to follow best practices to ensure your code is easy to understand, extend, and maintain.

What is SOLID?

SOLID is a set of five principles that help to write clean, maintainable, and scalable code. These principles were introduced by Robert C. Martin and have become a cornerstone of modern software development.

In this blog post, we’ll go through each of these principles and discuss how they can be applied to JavaScript code effectively.

Single Responsibility Principle

The Single Responsibility Principle (SRP) states that a class should have only one reason to change. In other words, a class should have only one responsibility.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// Bad Example
class User {
  constructor(name, email) {
    this.name = name;
    this.email = email;
  }

  saveToDatabase() {
    // save user to the database
  }

  sendEmail() {
    // send confirmation email
  }
}

In the above example, the User class is responsible for both storing data and sending an email. A better approach would be to separate these responsibilities into two different classes:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// Good Example
class User {
  constructor(name, email) {
    this.name = name;
    this.email = email;
  }
}

class UserDatabase {
  save(user) {
    // save user to the database
  }
}

class EmailService {
  sendConfirmationEmail(user) {
    // send confirmation email
  }
}

Open/Closed Principle

The Open/Closed Principle (OCP) states that a class should be open for extension but closed for modification. This means that you should be able to extend the behavior of a class without modifying its source code.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// Bad Example
class Shape {
  calculateArea() {
    throw new Error("This method should be overridden");
  }
}

class Square extends Shape {
  calculateArea() {
    // calculate area of a square
  }
}

class Circle extends Shape {
  calculateArea() {
    // calculate area of a circle
  }
}

In the above example, every time we add a new shape, we have to modify the Shape class. A better approach would be to use the Strategy pattern to allow for easy extension without modifying existing code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Good Example
class Shape {
  constructor(calculator) {
    this.calculator = calculator;
  }

  calculateArea() {
    return this.calculator.calculateArea();
  }
}

class Square {
  calculateArea() {
    // calculate area of a square
  }
}

class Circle {
  calculateArea() {
    // calculate area of a circle
  }
}

Liskov Substitution Principle

The Liskov Substitution Principle (LSP) states that objects of a superclass should be replaceable with objects of a subclass without affecting the functionality of the program.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// Bad Example
class Bird {
  fly() {
    // implementation for flying
  }
}

class Ostrich extends Bird {
  fly() {
    throw new Error("Ostrich cannot fly");
  }
}

In the above example, an Ostrich is a subclass of Bird but does not support the fly method, violating the LSP. A better approach would be to have a separate CanFly interface:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// Good Example
class Bird {
  fly() {
    // implementation for flying
  }
}

class Ostrich {
  // no fly method
}

Interface Segregation Principle

The Interface Segregation Principle (ISP) states that a client should not be forced to depend on interfaces it does not use. In JavaScript, this can be applied to ensure that clients only have to implement the methods they are interested in.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// Bad Example
class Worker {
  work() {
    // common work implementation
  }

  eat() {
    // common eat method
  }
}

class Developer extends Worker {
  code() {
    // specific to developers
  }

  design() {
    // specific to developers
  }
}

In the above example, a Developer class that extends Worker is forced to implement the eat method, violating the ISP. A better approach would be to have separate interfaces for different tasks:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Good Example
class Workable {
  work() {
    // common work implementation
  }
}

class Eatable {
  eat() {
    // common eat method
  }
}

class Developer extends Workable {
  code() {
    // specific to developers
  }
}

class Designer extends Workable {
  design() {
    // specific to designers
  }
}

Dependency Inversion Principle

The Dependency Inversion Principle (DIP) states that high-level modules should not depend on low-level modules, but rather both should depend on abstractions.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// Bad Example
class EmailService {
  sendConfirmationEmail() {
    // send confirmation email
  }
}

class UserController {
  constructor() {
    this.emailService = new EmailService();
  }
}

In the above example, the UserController directly depends on the EmailService, violating the DIP. A better approach would be to use dependency injection:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// Good Example
class EmailService {
  sendConfirmationEmail() {
    // send confirmation email
  }
}

class UserController {
  constructor(emailService) {
    this.emailService = emailService;
  }
}

Conclusion

In conclusion, following the SOLID principles in JavaScript can lead to cleaner, more maintainable, and scalable code. By separating concerns, using design patterns, and embracing modular and dependency-injected architecture, developers can create code that is easier to understand, extend, and maintain.

By understanding and applying these principles, developers can write more robust and flexible code that can adapt to changing requirements and scale with the growth of the project.

I hope this blog post has helped you understand the importance of SOLID principles in JavaScript and how to apply them to write clean and maintainable code. Happy coding!


➡️ JS spread operator


⬅️ Bit-bang in embedded development


Go back to Posts.