---
title: "Single Responsibility & Advanatges in Compiled Languages"
description: "Learn how the Single Responsibility Principle supercharges compiled languages with faster builds, safer changes, and better maintainability."
slug: "single-responsibility-and-advanatges-in-compiled-languages"
url: "https://furkanbaytekin.dev/blogs/single-responsibility-and-advanatges-in-compiled-languages"
author: "Furkan Baytekin"
category: "Software"
tags: []
published: 2025-03-10T08:47:56Z
updated: 2025-03-10T08:47:56Z
readTime: 5
cover: "https://myblogcms.furkanbaytekin.dev/uploads/cover/single-responsibility-and-advanatges-in-compiled-languages+1741526950634"
---

When it comes to writing clean, maintainable code, few ideas shine as brightly as the **Single Responsibility Principle (SRP)**. Part of the SOLID design principles, SRP states that a class or module should have only one reason to change, meaning it should stick to one job. This isn’t just a theoretical nicety; it’s a practical superpower, especially in backend development and even more so in compiled languages like Java or C++. Let’s break it down with a real-world example: user authentication in a web app.

## The Tangled Mess: A UserService Gone Wild

Imagine you’re building a backend for a web application, and you need to authenticate users. Here’s a `UserService` class that tries to do it all:

```javascript
class UserService {
  constructor() {
    this.database = new DatabaseConnection();
  }

  authenticateUser(email, password) {
    // Validate input
    if (!email || !password) {
      throw new Error("Email and password are required");
    }
    if (!email.includes("@")) {
      throw new Error("Invalid email format");
    }

    // Query database
    const user = this.database.query("SELECT * FROM users WHERE email = ?", [email]);
    if (!user) {
      throw new Error("User not found");
    }

    // Check password
    const isValid = bcrypt.compareSync(password, user.hashedPassword);
    if (!isValid) {
      throw new Error("Incorrect password");
    }

    // Generate JWT token
    const token = jwt.sign({ id: user.id, email }, "secret_key", { expiresIn: "1h" });
    return token;
  }
}
```

This class is a jack-of-all-trades: it validates input, talks to the database, checks passwords, and generates JWT tokens. At first glance, it might seem efficient, everything’s in one place! But here’s the catch: it’s a maintenance nightmare. If the database schema changes, or you need a stricter email validation rule, or the JWT library gets swapped out, you’re editing this *one* class for entirely unrelated reasons. It’s a ticking time bomb for bugs and frustration.

## Refactoring with SRP: One Job, One Class

Let’s apply SRP and split this into focused components. Here’s the refactored version:

```javascript
class UserValidator {
  validateCredentials(email, password) {
    if (!email || !password) {
      throw new Error("Email and password are required");
    }
    // Do not focus this, we are not going to implement this
    if (!email.includes("@")) {
      throw new Error("Invalid email format");
    }
  }
}

class UserRepository {
  constructor(database) {
    this.database = database;
  }

  findUserByEmail(email) {
    const user = this.database.query("SELECT * FROM users WHERE email = ?", [email]);
    if (!user) {
      throw new Error("User not found");
    }
    return user;
  }
}

class PasswordService {
  comparePassword(plainText, hashedPassword) {
    return bcrypt.compareSync(plainText, hashedPassword);
  }
}

class TokenService {
  generateJWT(userId, email) {
    return jwt.sign({ id: userId, email }, "secret_key", { expiresIn: "1h" });
  }
}

class UserService {
  constructor(validator, repo, passwordService, tokenService) {
    this.validator = validator;
    this.repo = repo;
    this.passwordService = passwordService;
    this.tokenService = tokenService;
  }

  authenticateUser(email, password) {
    this.validator.validateCredentials(email, password);
    const user = this.repo.findUserByEmail(email);
    const isValid = this.passwordService.comparePassword(password, user.hashedPassword);
    if (!isValid) {
      throw new Error("Incorrect password");
    }
    return this.tokenService.generateJWT(user.id, user.email);
  }
}
```

Now each class has *one job*:
- `UserValidator` checks input.
- `UserRepository` fetches user data.
- `PasswordService` verifies passwords.
- `TokenService` handles tokens.
- `UserService` orchestrates the flow without doing the heavy lifting itself.

This is cleaner, but why does it matter so much more in compiled languages? Let’s dive into the advantages.

## Why Compiled Languages Love SRP

In languages like Java, C++, or Rust, code gets compiled into machine code or bytecode before execution. SRP amplifies the benefits here in ways interpreted languages (like JavaScript or Python) don’t quite match. Here’s how:

1. **Selective Recompilation: Build Smarter, Not Harder**

- In a compiled language, say Java, each class lives in its own `.java` file. When you change `PasswordService.java` (e.g., to use a new hashing algorithm), only that file needs to recompile into `PasswordService.class`. Tools like Maven or Gradle detect this and skip recompiling `UserService.class`, `UserRepository.class`, etc. With the tangled version, *any* change—validation, database, or tokens—forces a full recompile of the monolithic `UserService.java`. In large projects, this saves serious build time.

2. **Smaller Patches: Deploy Less, Stress Less**

- After compilation, deploying updates becomes leaner. If you’re patching a server, you might only need to replace `PasswordService.class` in the `.jar` file, leaving the rest intact. In the tangled version, you’re redeploying the whole `UserService.class` even for a tiny tweak. For systems programming in C++, this could mean patching a single `.o` file instead of relinking an entire binary.

3. **Reduced Side Effects: Change with Confidence**

- SRP isolates responsibilities, so a change in one area won’t accidentally break another. Imagine adding a new validation rule in the tangled `UserService`. You might fat-finger a line and break the database query logic. With SRP, `UserValidator` is a separate entity—tweak it, recompile it, and `UserRepository` stays safe. This isolation is enforced at compile time, giving you a safety net interpreted languages lean on runtime to catch.

## Beyond Compilation: A Universal Win

Even outside the compiled world, SRP shines. That reduced risk of side effects? It’s a lifesaver everywhere. In the refactored version, if `TokenService` fails to generate a JWT, it won’t crash your database calls in `UserRepository`. Debugging becomes a breeze—you know exactly where to look. Plus, teams can work on separate components without stepping on each other’s toes.

## The Takeaway

The Single Responsibility Principle isn’t just a buzzword—it’s a practical tool for building better software. In compiled languages, it turbocharges your workflow with faster builds and leaner deployments. In our `UserService` example, splitting duties means a change to password logic doesn’t force a full rebuild or risk breaking unrelated code. Whether you’re slinging Java bytecode or C++ binaries, SRP keeps your code focused, your builds efficient, and your sanity intact.

So next time you’re tempted to cram everything into one class, pause. Give each piece its own job. Your future self—and your compiler—will thank you.

---

Album of the day:

<iframe style="border-radius:12px" src="https://open.spotify.com/embed/album/2YMdIva9wRWuFYrUPyM2e9?utm_source=generator" width="100%" height="152" frameBorder="0" allowfullscreen="" allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture" loading="lazy"></iframe>
