There is so much confusion over this.
Bob Martin (Uncle Bob) from his clean code blog: The Single Responsibility Principle (https://blog.cleancoder.com/uncle-bob/2014/05/08/SingleReponsibilityPrinciple.html) states the following:
"Another wording for the Single Responsibility Principle is: Gather together the things that change for the same reasons. Separate those things that change for different reasons. If you think about this you’ll realize that this is just another way to define cohesion and coupling. We want to increase the cohesion between things that change for the same reasons, and we want to decrease the coupling between those things that change for different reasons."
"However, as you think about this principle, remember that the reasons for change are people. It is people who request changes."
he also defines it as:
"A module should be responsible to one, and only one, actor."
What does the SRP NOT mean?
- It does not mean to make every class as small as possible so that it only does one thing because it is small.
- It does not mean that there should only ever be the need for a single change to a class when it is being maintained.
- It does not mean that a class or function or module must be of any size at all.
what DOES it mean?
He's purposefully vague when he talks about 'things' because this has different meaning in different environments but most of us can relate it to a package or namespace for practical intent although for some it may be a class or even file. It pertains to the fundamental 'modularizing' element of your environment whatever that is.
In use case terminology we often think of people having roles. This is clearly what he means in his example in the book "Clean Architecture" where he defines reasons for change in terms of the business roles of individuals using information from the system.
Now think of the use case itself. There may be several use cases that pertain just to a specific role or (actor). They may not have to change for any other purpose than to satisfy the requirements of the individual fulfilling this role. You can think of the use case as a duty of a person in a role or the "reason" for writing the code. There are also business "reasons" why the person in this role would want to change the code.
You may be implementing a single use case or even a few closely related use cases for a single role. If this is your case then these "highly cohesive" classes and functions should all be contained in the same module to satsify the SRP.
There may be several classes and possibly many functions per class and that is perfectly fine for all code necessary to satisfy the use case because the number of classes and functions involved are not part of the principle.
So code that is required to perform a duty for a user in a specific role belongs together in a module. Code for other duties for other user roles belong in different modules. Other duties may belong in a different module if they are not closely related even if they are for the same role.
We are gathering together the code that changes together for individuals in the same role and separating code that changes for different roles because their use cases or needs for even the same information are different or potentially divergent over time.
Modules are right-sized when they meet the principle. Not when they have x number of lines or consist of only a single class with a single function in them. These are NOT applications of the principle in any way, shape or form. Any correlation between the size of the classes or number of functions or methods in classes and the SRP is purely coincidental.
After reading many other people's interpretation of the SRP I think I understand now why there is so much confusion. People are applying the 'principle' at multiple levels. I agree that a function (the smallest unit of lines of code related by name in your language) should be as specific as possible to the meaning of that name and that it should represent a single simple task.
But when applied to classes or modules the SRP has a completely different meaning as I have described in this post. It is by far the more useful application of the principle as it will keep you out of architectural trouble which is much harder to overcome than a simple case of breaking a function down into succinct operations which should always be done for the sake of readability but hardly worth declaring a 'principle' for.
Note also that it is unhelpful to simply apply this one principle and ignore all the others. There are architectural reasons for separating concerns (database access, models, framework, I/O and other classes). These mostly have to do with mitigating risks. That should be the focus of deciding which modules to create.
If the modules you create contain code that is:
- highly cohesive (highly interrelated) and
- constitute an efficient solution to the problem and
- does not co-mingle code specifically designed to satisfy divergent high-level business objectives and
- you have sufficiently encapsulated complexity away from other concerns and
- the functions in it have been factored enough to make them easy to understand
- module separation is true to the design (i.e. onion architecture or whatever pattern(s) etc... you are implementing)
then you should be satisfied with your solution with respect to the SRP.