Dear Code, you had One Job! by Hardik Kardam on October 31, 2025 9 views

Hey everyone! Ever been super proud of a chunk of code you wrote, only to have it blow up in your face later? 🤦‍♂️ That’s exactly what happened to me recently.

I was building a task manager for a surgery module. It was a pretty complex system with all sorts of entities: surgery_master, TaskMapping, surgery_booking, task_master, and a full-blown Task entity. My code was split into a couple of services and data classes, and I honestly thought I’d nailed it. The code was clean, efficient, and easy to read. I was so proud of it!

Then, we introduced new requirements for general OPD tasks, in-patient department tasks, and inventory staff tasks. Suddenly, my beautifully simple code wasn’t so simple anymore. It was tightly coupled and a total nightmare to change. Adding or removing new types of tasks felt like pulling a thread on a sweater, the whole thing would unravel.

That’s when I had a major realization: it’s not about how few lines of code you write or how efficient your logic is. What really matters is writing loosely coupled, modular code. This experience sent me down a rabbit hole of research into coding principles that prevent this kind of headache.

I hope this blog helps you on your own software development journey. Let’s dive in!

But what exactly do I mean by that?

What exactly does ‘one job’ mean in the world of code? Let’s take a wild ride to understand this. Imagine a passionate young boy, Arjun, an amazing cook. He decides to open a small cafe. As he’s just starting, he’s wearing all the hats 🧑‍🍳:

  • He’s the Chef (his main passion: cooking delicious food).
  • He’s also the Waiter (taking orders).
  • He’s the Cashier (calculating bills and taking payments).

Arjun is doing too much. His primary responsibility is cooking, but these other tasks, while necessary, distract him and make his life harder. If he gets a big order, he might burn something while rushing to answer the phone or greet a customer. If he messes up the inventory, he might not have ingredients for his best dishes.

This is what might be happening with your classes/modules & functions too!

A Growing Problem, Arjun’s work might keep getting complex:

  • As a Waiter: He is unable to explain the menu or take large orders since the food on the gas could burn.
  • As a Chef: The food might be overcooked or undercooked because he is constantly interrupted by customers needing orders taken or bills paid. Quality suffers.
  • As a Cashier: There’s a long line at the register because he is stuck in the kitchen. He might make mistakes calculating bills due to being flustered from cooking.

Massive Complexity:

Think of the mental gymnastics: remembering dozens of menu items and prices, cooking multiple dishes simultaneously, handling payment methods, managing tables, and dealing with customer complaints – all while switching roles every few minutes. Every task needs to be performed without interfering with the others, which is nearly impossible.

The problem with this:

  • Can’t Scale: What if he wants to start taking online orders or deliveries?
  • Hard to Maintain: What if tax rates change, or you need to add discounts to bills, or new payment methods?

Sound familiar? Because this isn’t just about a fictional super-staff. In the world of code, we often fall into the same trap. Maybe you’ve built a class or function to handle some ‘common’ functionality – let’s call it X. Modules A and B use X to save lines of code. But then module C comes along, needing only a part of what X does. Suddenly, you’re asking: ‘Can I just cram this into X?’ ‘Should I add exceptions to X?’ Or worse, ‘Do I really have to change A, B, and C just to keep X viable for everyone?’

What’s the solution?

How do we escape this tangled mess? I’ve learned it the hard way, and I’m here to spare you some headaches. The solution is surprisingly simple, and it leads to code that’s:

  • Modular
  • Scalable
  • Maintainable
  • Easy-to-understand

We achieve this by embracing the ‘S’ in the famous SOLID principles: The Single Responsibility Principle


The SRP Solution – Single Responsibility Principle

Arjun’s situation highlights that even if one person can do all these jobs, it’s not the best way to run things long-term. As the cafe grows, Arjun should delegate these other responsibilities to dedicated people.

Instead of Arjun running the one-man show, he hires staff to handle responsibilities:

  • A dedicated Waiter (focused on taking orders and serving food efficiently).
  • A precise Cashier (designed for accurate billing and payment processing).

Each of these specialized staff members performs their “one job” exceptionally well. They are easier to train, manage, and replace. You can hire the right expert for the right role without them carrying around the baggage of unnecessary skills for other restaurant operations. If the chef is sick, the waiter can still take orders, and the cashier can still process payments for past orders. The restaurant keeps running!

So, what’s the takeaway from this situation? It’s a vivid illustration of why focusing on a single job is paramount:

  • A Single ‘Reason to Change’: Just like trying to make Arjun better at cooking would inevitably impact his speed as a waiter or his accuracy as a cashier (and vice-versa), a class with multiple responsibilities means a change for one reason can accidentally break something completely unrelated.
  • Jack of all trades: Just like the old saying, the class ends up being a master of none. Trying to handle everything makes it bloated and inefficient. A class with too many responsibilities is like our struggling Arjun: it might be busy, but it’s not actually excelling at anything.
  • Reduced Complexity and Fragility: Arjun’s intertwined tasks made him incredibly brittle. Similarly, classes with too many interconnected tasks are harder to understand and maintain and are prone to breaking in unexpected ways.
  • A Clear Path Forward: The solution for Arjun was to create dedicated staff roles. For code, this means breaking down a ‘God class’ (a class that does too much) into smaller, focused classes, each with its own single responsibility.

It’s very important to define a responsibility first, and it may change later, and you may realize now the project needs more refactoring, but that’s okay; it will still be easier to refactor than writing classes/modules unorganized.

Here’s how I might have coded the solution (the problem): When I started learning to code, I would have tried to keep the code lines and classes to a minimum, thinking it was an optimal approach, but now I believe that:

“A good coder tries to keep the codebase small; an experienced coder tries to keep the code scalable.” ~ Hardik Kardam, 2025


Let’s look at a code example

public class ArjunCafe {
    private String name;
    private Map<String, Double> menuPrices;
    private Map<Integer, String[]> currentOrders;

    public ArjunCafe() {
        this("Arjun");
    }

    public ArjunCafe(String name) {
        this.name = name;
        this.menuPrices = new HashMap<>();
        this.menuPrices.put("Special Pasta", 15.0);
        this.menuPrices.put("Gourmet Burger", 18.0);
        this.menuPrices.put("Artisan Coffee", 5.0);
        this.currentOrders = new HashMap<>();
    }

    // Arjun's PRIMARY job: Cooking
    public void cook(String[] orderItems, int tableNumber) {
        System.out.println("\\n" + this.name + " (Chef): Carefully cooking " + String.join(", ", orderItems) + " for Table " + tableNumber + "...");
        // Imagine complex cooking logic here
        System.out.println("--- " + this.name + ": Delicious " + String.join(", ", orderItems) + " prepared! ---");
    }

    // Secondary job: Taking orders
    public void takeOrder(int tableNumber, String[] items) {
        System.out.println(this.name + " (Waiter): Taking order for Table " + tableNumber + ": " + String.join(", ", items));
        this.currentOrders.put(tableNumber, items);
        // If the ordering process changes (e.g., tablet orders), this method changes.
    }

    // Secondary job: Calculating bills
    public double calculateBill(int tableNumber) {
        if (this.currentOrders.containsKey(tableNumber)) {
            double totalBill = 0.0;
            String[] items = this.currentOrders.get(tableNumber);
            for (String item : items) {
                if (this.menuPrices.containsKey(item)) {
                    totalBill += this.menuPrices.get(item);
                } else {
                    System.out.println("Warning: Unknown item '" + item + "' for Table " + tableNumber + ".");
                }
            }
            System.out.printf(this.name + " (Cashier): Calculating bill for Table " + tableNumber + ". Total: $%.2f%n", totalBill);
            this.currentOrders.remove(tableNumber);
            return totalBill;
            // If tax rules change or new payment methods are added, this method changes.
        } else {
            System.out.println(this.name + ": No order to bill for Table " + tableNumber + ".");
            return 0.0;
        }
    }
}

The class ArjunCafe handles everything, including cooking, orders & bills. This might look simple, but this might grow unexpectedly large and messy, which adds to maintenance efforts and difficulty in scaling.

Now, imagine we try to add another staff class (a cook) that can only cook. We cannot inherit this class to reuse the functions, as we would need to implement other functions as well.

public class Chef extends ArjunCafe {

    /**
     * Constructor for the Chef class. Calls the superclass constructor to set the name.
     */
    public Chef() {
        super("Karan");
    }

    /**
     * Overrides the takeOrder method to throw an exception.
     * This is because taking orders is not a chef's job.
     * @param tableNumber The table number.
     * @param items The items being ordered.
     * @throws UnsupportedOperationException whenever called.
     */
    @Override
    public void takeOrder(int tableNumber, String[] items) {
        throw new UnsupportedOperationException("A chef's primary job is cooking, not taking orders.");
    }

    /**
     * Overrides the calculateBill method to throw an exception.
     * This is because calculating bills is not a chef's job.
     * @param tableNumber The table number.
     * @return Never returns a value.
     * @throws UnsupportedOperationException whenever called.
     */
    @Override
    public double calculateBill(int tableNumber) {
        throw new UnsupportedOperationException("A chef's primary job is cooking, not calculating bills.");
    }
}

This defeats the purpose of inheritance, so either we do a big refactoring and split the ArjunCafe class into multiple classes: Cook, TakeOrder & Bill classes, hence implementing the single responsibility principle, or we create a completely new class and keep the repeated code problems and not maintain any common interface between them.

public class Chef {
    public void cook(String[] orderItems, int tableNumber) {
        System.out.println("\\n" + this.name + " (Chef): Carefully cooking " + String.join(", ", orderItems) + " for Table " + tableNumber + "...");
        // Imagine complex cooking logic here
        System.out.println("--- " + this.name + ": Delicious " + String.join(", ", orderItems) + " prepared! ---");
    }
}

We can improve on this and create classes with more defined responsibilities (a single responsibility):

/**
 * Main class to demonstrate the refactored ArjunCafe system,
 * adhering to the Single Responsibility Principle.
 */
public class ArjunCafe {
    private final String name;
    private final Waiter waiter;
    private final Chef chef;
    private final Cashier cashier;

    /**
     * Constructor for the ArjunCafe.
     * It initializes the specialized roles: Waiter, Chef, and Cashier.
     * @param name The name of the cafe's manager.
     */
    public ArjunCafe(String name) {
        this.name = name;
        this.waiter = new Waiter(new HashMap<>()); // Waiter needs to manage orders
        this.chef = new Chef();
        this.cashier = new Cashier();
    }

    /**
     * A public method to take a customer's order.
     * This delegates the task to the Waiter.
     * @param tableNumber The table number placing the order.
     * @param items The items being ordered.
     */
    public void takeOrder(int tableNumber, String[] items) {
        waiter.takeOrder(tableNumber, items);
    }

    /**
     * A public method to start the cooking process.
     * This delegates the task to the Chef.
     * @param tableNumber The table number for the order.
     */
    public void prepareOrder(int tableNumber) {
        if (waiter.getCurrentOrders().containsKey(tableNumber)) {
            String[] items = waiter.getCurrentOrders().get(tableNumber);
            chef.cook(items, tableNumber);
        } else {
            System.out.println(this.name + ": No order found for Table " + tableNumber + " to cook.");
        }
    }

    /**
     * A public method to calculate the bill.
     * This delegates the task to the Cashier.
     * @param tableNumber The table number to bill.
     */
    public void calculateAndPrintBill(int tableNumber) {
        double total = cashier.calculateBill(waiter.getCurrentOrders().get(tableNumber), tableNumber);
        if (total > 0) {
            waiter.clearOrder(tableNumber); // The waiter clears the order after the bill is paid.
        }
    }
}

/**
 * The Chef class is now solely responsible for cooking food.
 * It has no knowledge of orders or bills.
 */
class Chef {
    /**
     * Cooks the ordered items for a specific table.
     * @param orderItems An array of food items to cook.
     * @param tableNumber The table number the food is for.
     */
    public void cook(String[] orderItems, int tableNumber) {
        System.out.println("\\nChef: Carefully cooking " + String.join(", ", orderItems) + " for Table " + tableNumber + "...");
        // Imagine complex cooking logic here
        System.out.println("--- Chef: Delicious " + String.join(", ", orderItems) + " prepared! ---");
    }
}

/**
 * The Waiter class is now solely responsible for taking orders.
 * It manages the current orders and has no knowledge of cooking or bills.
 */
class Waiter {
    private final Map<Integer, String[]> currentOrders;

    /**
     * Constructor for the Waiter.
     * @param orders A map to store current orders by table number.
     */
    public Waiter(Map<Integer, String[]> orders) {
        this.currentOrders = orders;
    }

    /**
     * Takes an order from a table and stores it.
     * @param tableNumber The table number.
     * @param items The items ordered.
     */
    public void takeOrder(int tableNumber, String[] items) {
        System.out.println("Waiter: Taking order for Table " + tableNumber + ": " + String.join(", ", items));
        currentOrders.put(tableNumber, items);
    }

    /**
     * Clears an order after the bill has been paid.
     * @param tableNumber The table number to clear.
     */
    public void clearOrder(int tableNumber) {
        if (currentOrders.containsKey(tableNumber)) {
            currentOrders.remove(tableNumber);
            System.out.println("Waiter: Order for Table " + tableNumber + " has been cleared.");
        }
    }

    /**
     * Gets the map of current orders.
     * @return The map of current orders.
     */
    public Map<Integer, String[]> getCurrentOrders() {
        return this.currentOrders;
    }
}

/**
 * The Cashier class is now solely responsible for managing menu prices
 * and calculating the final bill.
 */
class Cashier {
    private final Map<String, Double> menuPrices;

    /**
     * Constructor for the Cashier, which sets up the menu.
     */
    public Cashier() {
        this.menuPrices = new HashMap<>();
        this.menuPrices.put("Special Pasta", 15.0);
        this.menuPrices.put("Gourmet Burger", 18.0);
        this.menuPrices.put("Artisan Coffee", 5.0);
    }

    /**
     * Calculates the total bill for an order.
     * @param orderItems The items from the order.
     * @param tableNumber The table number for which the bill is being calculated.
     * @return The total bill amount.
     */
    public double calculateBill(String[] orderItems, int tableNumber) {
        if (orderItems == null) {
            System.out.println("Cashier: No order to bill for Table " + tableNumber + ".");
            return 0.0;
        }

        double totalBill = 0.0;
        for (String item : orderItems) {
            if (menuPrices.containsKey(item)) {
                totalBill += menuPrices.get(item);
            } else {
                System.out.println("Warning: Unknown item '" + item + "' for Table " + tableNumber + ".");
            }
        }
        System.out.printf("Cashier: Calculating bill for Table " + tableNumber + ". Total: $%.2f%n", totalBill);
        return totalBill;
    }
}

Here we have reduced the coupling and made the code modular & easier to manage and scale.

Now, what if we want hybrid roles?

Let’s say we have a class that can takeOrder & cook we can do this:

public class WaiterChef {
    private final String name;
    private final Waiter waiter;
    private final Chef chef;

    /**
     * Constructor for the WaiterChef.
     * It initializes the specialized roles: Waiter and Chef
     * @param name The name of the cafe's staff.
     */
    public WaiterChef(String name) {
        this.name = name;
        this.waiter = new Waiter(new HashMap<>()); // Waiter needs to manage orders
        this.chef = new Chef();
    }

    /**
     * A public method to take a customer's order.
     * This delegates the task to the Waiter.
     * @param tableNumber The table number placing the order.
     * @param items The items being ordered.
     */
    public void takeOrder(int tableNumber, String[] items) {
        waiter.takeOrder(tableNumber, items);
    }

    /**
     * A public method to start the cooking process.
     * This delegates the task to the Chef.
     * @param tableNumber The table number for the order.
     */
    public void prepareOrder(int tableNumber) {
        if (waiter.getCurrentOrders().containsKey(tableNumber)) {
            String[] items = waiter.getCurrentOrders().get(tableNumber);
            chef.cook(items, tableNumber);
        } else {
            System.out.println(this.name + ": No order found for Table " + tableNumber + " to cook.");
        }
    }
}

Your code had one job, right? 😕 Sometimes, the answer is way simpler than you think. It’s not that your code had “one job“—it’s that it lacked a single responsibility. We’ve only just scratched the surface here, and the next step is to use Interfaces and Dependency Injection to make your classes even more flexible and independent. But I’ll save that topic for another day.

For now, the next time you sit down to write a new class or function, pause and ask yourself one simple question: “What is its one job?” this single thought is the first step toward building code that isn’t just functional today but also scalable, maintainable, and resilient for whatever challenges tomorrow brings. Your future self—and your team—will thank you.


References:

Geeksforgeeks – single responsibility (SOLID principle)

Single Responsibility Principle Explained – SOLID Design Principles

The Flaws of Inheritance (Inspiration)

https://muatik.medium.com/oop-solid-with-examples-d3dc310d72c3

The Kitchen Concurrency: Multitasking with Threads

About Author

Hardik Kardam

Solution Analyst

I'm fundamentally a problem solver who just happens to use technology as my favorite set of tools. My journey in the tech world is driven by insatiable curiosity—I'm a true tech enthusiast who loves diving into every corner of the field.