[EN] Granularity: The Art of Breaking the System into the Right Size

Designing an object-oriented system goes far beyond simply creating classes and distributing methods. One of the most difficult and crucial decisions lies in how to break the system into smaller parts — that is, how to decompose the domain into well-defined objects and responsibilities.

This task involves several variables: encapsulation, coupling, cohesion, performance, reusability, and flexibility. At the center of all this is a concept that, although rarely directly discussed, has a profound impact on the quality of the architecture: granularity.

What is granularity in object-oriented design?

Granularity, in the context of object-oriented programming, represents the level of division or fragmentation of a system into its components — especially classes and objects.

It’s like deciding whether a class should do many things or just one thing.

High vs. Low granularity

  • High granularity:

    Small and specialized classes, each with a well-defined responsibility.

    • Example: EmailFormatter, EmailSender, EmailLogger.
  • Low granularity:

    Large and generic classes, grouping multiple responsibilities.

    • Example: EmailService that formats, sends, logs, and handles errors.

Both approaches are valid depending on context, but ignoring this decision often leads to:

  • God classes that do everything,
  • Or an excess of microcomponents that are hard to understand and maintain.

A simple analogy

Think of granularity like the size of puzzle pieces:

  • Large pieces (low granularity) are easier to handle and build the image quickly but offer less precision.
  • Small pieces (high granularity) offer more detail and flexibility but take more time and attention to assemble.

In OO design, your task is to find the right size pieces to build the system with clarity, coherence, and balance.

The danger of extremes

  • Granularity that’s too low can lead to monolithic and hard-to-maintain systems.
  • Granularity that’s too high can lead to over-fragmented systems with high coordination complexity.

So granularity isn’t about “more or fewer classes” but about having classes of the right size for the right context.

Why does granularity matter in software design?

Choosing the appropriate level of granularity directly impacts the quality, maintainability, and evolution of the system. A well-structured project in terms of granularity tends to be more:

  • Cohesive: each class has a clear responsibility.
  • Decoupled: components communicate through well-defined contracts.
  • Easy to test: small units are easier to isolate.
  • Reusable: specialized components can be reused in other contexts.
  • Scalable and evolvable: changes in one part of the system cause less impact on others.

Relationship with SOLID principles

Granularity is closely related to various object-oriented design principles such as:

  • SRP (Single Responsibility Principle): the more granular a class is, the more likely it has a single, well-defined responsibility.
  • OCP (Open/Closed Principle): smaller, specific classes tend to be easier to extend without modification.
  • DIP (Dependency Inversion Principle): well-defined, cohesive components facilitate dependency abstraction.

Neglecting granularity can lead to breaking these principles, resulting in rigid, fragile, and hard-to-evolve code.

Practical examples of granularity

Example with low granularity (God Class)

public class OrderService {
    public void createOrder(Order order) {
        validateOrder(order);
        calculateTotal(order);
        saveToDatabase(order);
        sendConfirmationEmail(order);
        logOrder(order);
    }

    // multiple methods with different responsibilities
}

This example concentrates multiple responsibilities in a single class: validation, calculation, persistence, communication, and logging.

Refactored with more appropriate granularity

public class OrderService {
    private final OrderValidator validator;
    private final OrderCalculator calculator;
    private final OrderRepository repository;
    private final EmailSender emailSender;
    private final OrderLogger logger;

    public void createOrder(Order order) {
        validator.validate(order);
        calculator.calculate(order);
        repository.save(order);
        emailSender.send(order);
        logger.log(order);
    }
}

Now, each responsibility is in a separate, specialized class with high cohesion. The OrderService acts as an orchestrator of these actions.

Result:

✔️ More readable

✔️ More testable

✔️ More flexible

✔️ Less prone to side effects

How to find the right balance?

Granularity is not fixed — it depends on the context. But there are some best practices that help guide your decisions:

Tips for balancing granularity

  1. Start simple and evolve over time

    • Don’t over-split prematurely. Refactor when a class takes on multiple responsibilities.
  2. Use SRP as your compass

    • Always ask: Is this class doing more than one thing? If so, it may be time to split.
  3. Avoid unnecessary abstractions

    • Creating classes just for the sake of it (e.g., Manager, Handler, Helper) can add complexity with no real benefit.
  4. Watch for signs of poor granularity

    • Huge methods, generic names, difficulty in testing or reuse can indicate low granularity.
  5. Prefer granularity that supports reuse and maintainability

    • Small, cohesive components are easier to understand and adapt.

Conclusion

Granularity is one of those architectural decisions that often go unnoticed but make all the difference in the quality of an object-oriented system.

By understanding how to break your system into the “right size,” you build cleaner, more flexible, and sustainable software.

Not everything needs to be micro, and not everything should be monolithic — the secret is in finding the ideal balance for your context.

Reflect: how are your project’s classes today? Are they too big? Or fragmented beyond reason?

Leave a Reply