Home/Blog/software engineering
Back to all articles
software engineering#Clean Code#SOLID Principles#Software Architecture

Writing Clean Code: SOLID Principles & Modern TypeScript Refactoring Patterns

RB
Rajput BhavinArchitect
Founder, Rajput Bhavin Engineering
24 August 2026•11 min read

Quick Answer: What is Clean Code in Modern Software Engineering?#

⚡ Quick Answer / Executive Summary
Clean Code is software that is intentionally architected to be readable, testable, maintainable, and adaptable over long horizons. It adheres to the SOLID principles (Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation, Dependency Inversion), enforces intention-revealing naming conventions, minimizes side effects through pure functions and immutability, and isolates domain business logic from external frameworks and database drivers.

Why Code Quality Determines Engineering Velocity#

In early-stage startups, developers frequently rush to ship features with quick-and-dirty hacks, believing it saves time. However, as the codebase grows:

  • ✦Adding a single new feature takes weeks instead of hours because touching one file triggers unexpected bugs in ten unrelated modules.
  • ✦Onboarding new software engineers takes months due to tangled spaghetti dependencies.
  • ✦Unit testing becomes impossible because business logic is tightly coupled to UI frameworks or database ORM models.

At Rajput Bhavin Engineering, we treat clean code as an essential engineering discipline that keeps development velocity high as products scale.


The SOLID Principles: Real-World TypeScript Refactoring#

Architecture Blueprint
System Flow
┌─────────────────────────────────────────────────────────────┐
│                    The 5 SOLID Principles                   │
├─────┬───────────────────────┬───────────────────────────────┤
│ S   │ Single Responsibility │ One reason to change          │
│ O   │ Open / Closed         │ Open for extension, closed mod│
│ L   │ Liskov Substitution   │ Subtypes must honor contracts │
│ I   │ Interface Segregation │ Granular, focused interfaces  │
│ D   │ Dependency Inversion  │ Depend on abstractions (DI)   │
└─────┴───────────────────────┴───────────────────────────────┘

#1. Single Responsibility Principle (SRP)

“A module, class, or function should have one, and only one, reason to change.”

❌ Bad (Violates SRP):

A single class that calculates invoices, saves them to the database, and sends email notifications:
TypeScript
Production Code
class InvoiceManager {
  calculateTotal(items: Item[]): number { /* Math */ }
  saveToDatabase(invoice: Invoice): void { /* SQL Query */ }
  sendEmailNotification(userEmail: string): void { /* SMTP Fetch */ }
}

✅ Clean (Adheres to SRP):

Decompose into three dedicated, single-purpose classes:
TypeScript
Production Code
class InvoiceCalculator {
  calculateTotal(items: Item[]): number { /* Pure Math */ }
}

class InvoiceRepository {
  async save(invoice: Invoice): Promise<void> { /* SQL */ }
}

class NotificationService {
  async sendEmail(to: string, template: string): Promise<void> { /* Email */ }
}

#2. Open/Closed Principle (OCP)

“Software entities should be open for extension, but closed for modification.”

❌ Bad (Violates OCP):

Using massive `switch` statements that require modifying core logic every time a new payment method is added:
TypeScript
Production Code
function processPayment(type: 'stripe' | 'razorpay' | 'crypto', amount: number) {
  if (type === 'stripe') { /* Stripe Logic */ }
  else if (type === 'razorpay') { /* Razorpay Logic */ }
  else if (type === 'crypto') { /* Crypto Logic */ }
  // Adding PayPal forces us to modify and re-test this entire function!
}

✅ Clean (Adheres to OCP):

Use strategy interfaces and polymorphic implementations:
TypeScript
Production Code
interface IPaymentProcessor {
  charge(amount: number): Promise<PaymentReceipt>;
}

class StripeProcessor implements IPaymentProcessor {
  async charge(amount: number) { /* Stripe SDK */ }
}

class RazorpayProcessor implements IPaymentProcessor {
  async charge(amount: number) { /* Razorpay SDK */ }
}

// Open for extension: new processors (e.g. PayPalProcessor) are added without modifying existing code
class CheckoutService {
  constructor(private paymentProcessor: IPaymentProcessor) {}

  async completeCheckout(amount: number) {
    return await this.paymentProcessor.charge(amount);
  }
}

#3. Dependency Inversion Principle (DIP)

“High-level modules should not depend on low-level modules. Both should depend on abstractions.”

❌ Bad (Tight Coupling):

TypeScript
Production Code
import { SendGridClient } from 'sendgrid';

class UserRegistrationService {
  private mailer = new SendGridClient(); // Hard dependency on SendGrid!

  async registerUser(email: string) {
    // Cannot easily unit test this service without sending real emails!
    await this.mailer.send({ to: email, text: 'Welcome!' });
  }
}

✅ Clean (Dependency Inversion):

TypeScript
Production Code
interface IMailer {
  send(to: string, message: string): Promise<void>;
}

class UserRegistrationService {
  constructor(private mailer: IMailer) {} // Injected abstraction

  async registerUser(email: string) {
    await this.mailer.send(email, 'Welcome!');
  }
}

// In unit tests, inject MockMailer. In production, inject SendGridMailer.

4 Practical Rules for Clean Naming & Functions#

  1. ✓Intention-Revealing Names: Avoid vague abbreviations (const d = 5;). Use clear domain terms (const maxAllowedLoginAttempts = 5;).
  2. ✓Pure Functions Over Side Effects: Write functions that receive inputs and return outputs without mutating external global variables.
  3. ✓Early Returns (Guard Clauses): Avoid deep nested if-else pyramids by validating preconditions and returning early:
TypeScript
Production Code
// Clean Guard Clause Pattern
function processRefund(order: Order) {
  if (!order.isPaid) throw new Error('Order not paid');
  if (order.isRefunded) throw new Error('Already refunded');
  if (order.isOver30DaysOld) throw new Error('Refund window expired');

  // Core execution path remains unnested
  return executeRefund(order);
}

Build Maintainable Software with Rajput Bhavin Engineering#

Eliminate technical debt and engineer scalable software architectures with systems architect Rajput Bhavin.

Software Engineering Consultation

Need help architecting your modern software stack?

Rajput Bhavin Engineering provides hands-on systems architecture, codebase reviews, and custom cloud software development for technology teams in India and worldwide.

RB

Rajput Bhavin

Systems Architect

Founder and Systems Architect at Rajput Bhavin Engineering in Gota, Ahmedabad. Specializes in high-concurrency cloud software, Next.js web applications, multi-tenant SaaS architecture, and autonomous AI systems.

Connect Now