The Misunderstood SRP
I believe SRP's popularity can be attributed to its intuitive name: Single Responsibility Principle. The principle suggests a clear concept: things should have a single, easy-to-understand responsibility. Otherwise, it becomes too complex, and you should consider splitting it up.
In his book 'Agile Software Development, Principles, Patterns, and Practices,' Robert C. Martin introduced a different definition:
A class should have only one reason to change.
In the beginning of the chapter, he explained that the principle's origin is cohesion, the functional relatedness of a module. This aligns with the intuitive definition: a class that performs one well-defined thing is cohesive, while doing different things is not. However, Uncle Bob shifts the focus to the forces that cause a module to change.
Report Service
A common violation of SRP could be a class doing both presentation and persistence. These two are generally considered as different responsibilities, and almost everybody agrees they should be separated. A typical example is the ReportService
that calculates the contents of a report and then renders the output. But these are straightforward examples and they don't really show why we have ‘a reason to change' in the definition. While it seems that we can easily judge that these classes have multiple responsibilities, the reality is often less straightforward.
Someone might prefer to split the ReportService
into 3 parts: data collection, calculation, and presentation. They might argue that they just applied SRP, and their design is better than just separating it into two. Others might argue that it's needless complexity. Who would be right? Is there an objectively better design in this case? Does it depend on the complexity/size of the responsibilities? Does it depend on the developer's subjective preference? It looks like different developers can apply SRP to the same problem and might come up with different separations.
Sometimes, there are classes like the ReportService
that were never separated into different classes, and they never caused any issues during the lifetime of a project. Why is it that sometimes it causes issues and sometimes it doesn't? What's the point of a design principle if it's not clear when to use it or how to use it? Some articles even give the advice to try to describe the responsibility of a class, and if you must use 'and', that means you should split that class. But then again, how many ways can we word something like that? I can always choose a higher abstraction (e.g., the responsibility is to create the report) or go into details of the process which will lead to splitting. This is ambiguous at best. Maybe we misunderstood what a responsibility is here. Robert explicitly defines responsibility to be a reason to change, but why? What's the difference?
The reason to change
SRP was not meant to be used for the evaluation of a design without considering the context and the expected changes in the system. Strictly speaking, it doesn't even suggest separating presentation/persistence from business roles. The trouble we aim to avoid is the need to frequently change classes with multiple functionalities. Whenever you modify one part of that class, there's a risk of affecting or breaking another part. This leads to the need to retest more features, maybe seemingly unrelated ones. That's the symptom SRP aims to address - not through static analysis, but by considering what will change together frequently and separating those that won't.
Perhaps there's a system where new kinds of reports need to be created frequently, and the old ones can be removed completely. If these are disposable reports and it's rare that existing ones are modified, then there are no different reasons to change a report. In this case SRP doesn't suggest separating the responsibilities. On the other hand, if we're discussing a report that is a primary feature of the system, then we should consider separating these responsibilities. Experience shows that systems often undergo UI/design changes, and developers are expected to change the UI without breaking critical business functionality. How demoralizing it is when making a small UI change leads to a multiple-week-long high-risk project? The same applies to technical details like frameworks and databases. Experience also shows that developers often need to reconsider these choices, and oftentimes their hands are tied due to high coupling to the business logic. That's why developers often separate the UI and persistence layer from business logic, and that's the point of SRP. It's not about an 'and' when describing the responsibility; it's about anticipating the changes we need to make.