Clean Code in JavaScript
Clean Code is not just readable and understandable code, but also code that is easy to test, extend and maintain. In JavaScript, it's often difficult to maintain cleanliness, especially when we're looking for quick solutions. In this article, we'll get acquainted with the basic principles that help write better structured code.
These are: DRY, KISS, YAGNI and SOLID principles.
DRY (Don't Repeat Yourself)
// Bad example
function calculateRectangleArea(width, height) {
return width * height;
}
function calculateRectanglePerimeter(width, height) {
return 2 * width + 2 * height;
}
function describeRectangle(width, height) {
const area = width * height;
const perimeter = 2 * width + 2 * height;
return `Area: ${area}, Perimeter: ${perimeter}`;
}
Good version: we reuse the logic
function calculateRectangleArea(width, height) {
return width * height;
}
function calculateRectanglePerimeter(width, height) {
return 2 * (width + height);
}
function describeRectangle(width, height) {
const area = calculateRectangleArea(width, height);
const perimeter = calculateRectanglePerimeter(width, height);
return `Area: ${area}, Perimeter: ${perimeter}`;
}
Advantages:
- Changes are made in only one place
- Code becomes more understandable
KISS (Keep It Simple, Stupid)
// Bad example
function isEven(num) {
return num % 2 === 0 ? true : false;
}
Good version:
function isEven(num) {
return num % 2 === 0;
}
Advantages:
- Shorter and more readable code
- No hidden complexity
YAGNI (You Aren't Gonna Need It)
// Bad example - redundant functionality
function sendMessage(user, message, isUrgent = false, sendCopyToAdmin = false) {
// currently there's no need to send copy to admin
if (sendCopyToAdmin) {
// send to admin
}
// send to user
}
Good version:
function sendMessage(user, message) {
// send to user
}
Advantages:
- We don't add unused functionality
- Future changes will be made as needed
SOLID Principles in JavaScript
SOLID consists of 5 principles that help write more flexible and extensible code.
1. Single Responsibility Principle (SRP)
// Bad example
class UserManager {
createUser(userData) { /* create user */ }
sendWelcomeEmail(user) { /* send email */ }
}
// Good version
class UserManager {
createUser(userData) { /* create user */ }
}
class EmailService {
sendWelcomeEmail(user) { /* send email */ }
}
Advantages:
- Each class has only one responsibility
2. Open/Closed Principle (OCP)
// Bad example
function discount(price, type) {
if (type === 'regular') return price;
if (type === 'vip') return price * 0.9;
}
// Good version - extensible classes
class RegularCustomer {
getDiscount(price) {
return price;
}
}
class VipCustomer {
getDiscount(price) {
return price * 0.9;
}
}
Advantages:
- Code is open for extension, closed for modification
3. Liskov Substitution Principle (LSP)
// Bad example
class Bird {
fly() {}
}
class Penguin extends Bird {
fly() {
throw new Error("Penguins can't fly!");
}
}
// Good version - different bases
class Bird {}
class FlyingBird extends Bird {
fly() {}
}
class Penguin extends Bird {
swim() {}
}
Advantages:
- Subclasses can replace parent classes without errors
4. Interface Segregation Principle (ISP)
// Bad example - large interface
class Machine {
print() {}
scan() {}
fax() {}
}
// Good version - specialized interfaces
class Printer {
print() {}
}
class Scanner {
scan() {}
}
Advantages:
- Each class gets only the methods it needs
5. Dependency Inversion Principle (DIP)
// Bad example - hard dependency
class MySQLDatabase {
save(data) {}
}
class UserService {
constructor() {
this.db = new MySQLDatabase();
}
}
// Good version - dependency is passed
class UserService {
constructor(database) {
this.db = database;
}
}
Advantages:
- Individual components are replaceable
- Testing becomes easier