DEV Community

Cover image for Making SOLID Simple: A JavaScript Guide to Clean Code Principles
Suryansh Singh
Suryansh Singh Subscriber

Posted on

Making SOLID Simple: A JavaScript Guide to Clean Code Principles

When I first started diving into the world of software development, I often found myself overwhelmed by all the buzzwords and concepts flying around. One of the concepts that seemed particularly daunting was SOLID principles. It felt like something only "serious" developers needed to worry about. But as I grew more comfortable with coding, I realized that these principles are less about being fancy and more about writing code that doesn’t make you want to pull your hair out after a few months.

So, here’s my take on SOLID principles in JavaScript—a no-nonsense, practical guide that I wish I had when I started.

1. Single Responsibility Principle (SRP): One Job, Done Well

What is it?

The Single Responsibility Principle states that a class should have only one reason to change, meaning it should have only one job or responsibility.

Real-Life Analogy

Think of a barista at your favorite coffee shop. Their job is to make coffee. If they suddenly have to start fixing the espresso machine, serving pastries, and taking out the trash, things are going to get chaotic. Just like how the barista should focus on making coffee, your class should focus on doing one thing well.

Example in JavaScript:

Imagine you have a User class that handles user authentication, data validation, and database storage. It’s doing too much! By splitting these responsibilities into separate classes, you make your code easier to manage and maintain.

class UserAuthenticator {
  login(user) {
    // handle login
  }
}

class UserDataValidator {
  validate(user) {
    // validate user data
  }
}

class UserDatabase {
  save(user) {
    // save user to the database
  }
}
Enter fullscreen mode Exit fullscreen mode

2. Open/Closed Principle (OCP): Extend, Don’t Modify

What is it?

The Open/Closed Principle states that software entities should be open for extension but closed for modification. In other words, you should be able to add new functionality without changing existing code.

Real-Life Analogy:

Imagine your favorite gaming console. You can add new games, controllers, and accessories, but you don’t have to open it up and rewire it to do so. Similarly, you should be able to add new features to your code without changing its core structure.

Example in JavaScript:

Let’s say you have a Shape class with a method to calculate the area. If you need to add a new shape, like a triangle, you shouldn’t have to modify the existing class. Instead, extend it.

class Shape {
  area() {
    throw "Area method not implemented";
  }
}

class Rectangle extends Shape {
  constructor(width, height) {
    super();
    this.width = width;
    this.height = height;
  }

  area() {
    return this.width * this.height;
  }
}

class Circle extends Shape {
  constructor(radius) {
    super();
    this.radius = radius;
  }

  area() {
    return Math.PI * this.radius * this.radius;
  }
}
Enter fullscreen mode Exit fullscreen mode

3. Liskov Substitution Principle (LSP): Keep It Substitutable

What is it?

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

Real-Life Analogy:

Imagine renting a car. Whether you get a sedan or an SUV, you expect it to have the basic functionalities of a car: it should drive, steer, and stop. If your rental car required a completely different set of controls, you’d be in trouble! Similarly, subclasses should behave in a way that doesn’t break the expectations set by their parent class.

Example in JavaScript:

If you have a Bird class and a Penguin class that extends it, the Penguin should still behave like a Bird even if it can’t fly. It should still walk, eat, and maybe even swim.

class Bird {
  move() {
    console.log("Flies in the sky");
  }
}

class Penguin extends Bird {
  move() {
    console.log("Swims in the water");
  }
}

const myBird = new Bird();
const myPenguin = new Penguin();

myBird.move(); // Flies in the sky
myPenguin.move(); // Swims in the water
Enter fullscreen mode Exit fullscreen mode

4. Interface Segregation Principle (ISP): Tailor-Made Interfaces

What is it?

The Interface Segregation Principle suggests that clients should not be forced to implement interfaces they don’t use. Instead of having one large interface, you should create smaller, more specific ones.

Real-Life Analogy:

Imagine a restaurant where the chef also has to be the waiter, bartender, and dishwasher. It’s overwhelming and inefficient! Instead, each role should have its specific tasks. Similarly, your interfaces should be specialized and focused.

Example in JavaScript:

If you have a Worker interface that includes methods like buildHouse, paintHouse, and designHouse, a worker who only paints houses shouldn’t have to implement all the other methods. Break it down into smaller interfaces.

class Builder {
  build() {
    console.log("Building house...");
  }
}

class Painter {
  paint() {
    console.log("Painting house...");
  }
}

class Designer {
  design() {
    console.log("Designing house...");
  }
}
Enter fullscreen mode Exit fullscreen mode

5. Dependency Inversion Principle (DIP): Rely on Abstractions

What is it?

The Dependency Inversion Principle states that high-level modules should not depend on low-level modules. Both should depend on abstractions.

Real-Life Analogy:

Think about how you plug your phone charger into a wall socket. You don’t need to know the details of the electrical wiring inside the walls—all you need is the interface (the socket) to power your device. Similarly, your code should depend on abstractions (interfaces), not concrete implementations.

Example in JavaScript:

If you have a LightBulb class that directly controls a Switch class, you’re creating a tight coupling. Instead, both should depend on an interface like PowerSource.

class LightBulb {
  turnOn(powerSource) {
    powerSource.provideElectricity();
    console.log("Light is on");
  }
}

class Switch {
  constructor(powerSource) {
    this.powerSource = powerSource;
  }

  operate() {
    this.powerSource.togglePower();
  }
}

class PowerSource {
  provideElectricity() {
    console.log("Providing electricity");
  }

  togglePower() {
    console.log("Toggling power");
  }
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

Mastering the SOLID principles is like learning to cook with a set of proven recipes. Once you understand them, you can whip up code that’s not just functional but elegant and easy to maintain. So next time you find yourself in a coding conundrum, remember: there’s a principle for that!

Happy coding! 🚀

Top comments (1)

Collapse
 
jottyjohn profile image
Jotty John

Nice Article