I've always considered the single responsbility principle more of a philosophy than principle.
Robert C. Martin (Uncle Bob) restates the Single Responsibility Principle (linked to PDF):
There should never be more than one reason for a class to change
Quoted from the reply you got your post.
When we try to appease the SPR, we focus on the implementation rather than on the purpose.
Major mistake.
My understanding of the SRP is this, when an addition blurs the responsibility of an implementation, the focal point should be reconsidered.
For me, it is all about the relationships between purposes.
Sure, a user has to be authenticated and authorized regarding actions to take - but the authentication and authorization should be separated from the user.
It's not like you are going to try to authorize/authenticate a non-user anyway?
And thus, you should consider splitting implementation when the responsibilities of a class outgrows it purpose. After all, design patterns have purposes too.
Take a look at GRASP when you get a chance.
Of course, the YAGNI principle will tell you to apply SRP not before you really need it. But the question you should ask yourself is: do I need to apply SRP first and only when I have to actually change my code?
To my experience, the application of SRP gives you a benefit much earlier: when you have to find out where and how to apply a specific change in your code. For this task, you have to read and understand your existing functions and classes. This gets very much easier when all your functions and classes have a specific responsibility. So IMHO you should apply SRP whenever it makes your code easier to read, whenever it makes your functions smaller and more self-describing. So the answer is yes, it makes sense to apply SRP even for new code.
For example, when your printing code reads a document, formats the document and prints the result to a specific device, these are 3 clear separable responsibilities. So make at least 3 functions out of them, give them according names. For example:
void RunPrintWorkflow()
{
var document = ReadDocument();
var formattedDocument = FormatDocument(document);
PrintDocumentToScreen(formattedDocument);
}
Now, when you get a new requirement to change document formatting or another one to print to PDF, you know exactly at which of these functions or locations in code you have to apply changes, and even more important, where not.
So, whenever you come to a function you don't understand because the function does "too much", and you are not sure if and where to apply a change, then consider to refactor the function into separate, smaller functions. Don't wait until you have to change something. Code is 10x more often read than changed, and smaller functions are much easier to read. To my experience, when a function has a certain complexity, you can always split the the function into different responsibilities, independent of knowing which changes will come in the future. Bob Martin typically goes a step further, see the link I gave in my comments below.
EDIT: to your comment: The main responsibility of the outer function in the example above is not to print to a specific device, or to format the document - it is to integrate the printing workflow. Thus, at the abstraction level of the outer function, a new requirement like "docs should not be formatted anymore" or "doc should be mailed instead of printed" is just "the same reason" - namely "printing workflow has changed". If we talk about things like that, it is important to stick to the right level of abstraction.
Best Answer
If your class has 20 parameters in the constructor, it doesn't sound like your team quite knows what SRP is. If you have a class that does only one thing, how does it have 20 dependencies? That's like going on a fishing trip and bringing along a fishing pole, tackle box, quilting supplies, bowling ball, nunchucks, flame thrower, etc.... If you need all that to go fishing, you're not just going fishing.
That said, SRP, like most principles out there, can be over-applied. If you create a new class for incrementing integers, then yeah, that may be a single responsibility, but come on. That's ridiculous. We tend to forget that things like the SOLID principles are there for a purpose. SOLID is a means to an end, not an end in itself. The end is maintainability. If you are going to get that granular with the Single Responsibility Principle, it's an indicator that zeal for SOLID has blinded the team to the goal of SOLID.
So, I guess what I'm saying is... The SRP isn't your problem. It's either a misunderstanding of the SRP, or an incredibly granular application of it. Try to get your team to keep the main thing the main thing. And the main thing is maintainability.
Get people to design modules in a way that encourages ease of use. Think of each class as a mini API. Think first, "How would I like to use this class," and then implement it. Don't just think "What does this class need to do." The SRP does have a great tendency to make classes harder to use, if you don't put much thought into usability.
If you're looking for tips on refactoring, you can start doing what you suggested - create coarser-grained classes to wrap several others. Make sure the coarser-grained class is still adhering to the SRP, but on a higher level. Then you have two alternatives:
When you're finished refactoring (but before committing to the repository), review your work and ask yourself if your refactoring was actually an improvement to maintainability and ease of use.