Single Responsibility Principle – Applying to New Code

design-patternssingle-responsibility

The principle is defined as modules having one reason to change. My question is, surely these reasons to change are not known until the code actually starts to change?? Pretty much every piece of code has numerous reasons why it could possibly change but surely attempting to anticipate all of these and design your code with this in mind would end up with very poor code. Isn't it a better idea to only really start to apply SRP when requests to change the code start coming in? More specifically, when a piece of code has changed more than once for more than one reason, thus proving it has more than one reason to change. It sounds very anti-Agile to attempt to guess reasons for change.

An example would be a piece of code which prints a document. A request comes in to change it to print to PDF and then a second request is made to change it to apply some different formatting to the document. At this point you have proof of more than a single reason to change (and violation of SRP) and should make the appropriate refactoring.

Best Answer

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.