21

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.

12
  • 6
    @Frank - it IS actually commonly defined like that - see e.g. en.wikipedia.org/wiki/Single_responsibility_principle May 3, 2013 at 9:20
  • 1
    The way you're phrasing it is not the way I understand the definition of SRP.
    – Pieter B
    May 3, 2013 at 9:50
  • 2
    Every line of code has (at least) two reasons for being changed: It contributes to a bug or it interferes with a new requirement. May 3, 2013 at 9:58
  • 1
    @BartvanIngenSchenau: LOL ;-) If you see it this way, the SRP cannot be applied anywhere.
    – Doc Brown
    May 3, 2013 at 9:59
  • 1
    @DocBrown: You can if you don't couple SRP with changing source code. For example, if you interpret SRP as being able to give a full account of what a class/function does in one sentence without using the word and (and no weasel wording to get around that restriction). May 3, 2013 at 10:06

10 Answers 10

28

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.

5
  • I generally always develop with TDD so in my example I wouldn't physically have been able to keep all that logic in one module because it would be impossible to test. This is just a by-product of TDD and not because I'm deliberately applying SRP. My example had fairly clear, separate responsibilities so maybe not a good example. I think what I'm asking is, can you write any new piece of code and unequivocally say, yes this does not violate SRP? Aren't 'reasons to change' essentially defined by the business? May 3, 2013 at 9:56
  • 3
    @thecapsaicinkid: yes, you can (at least by immediate refactoring). But you will get very, very small functions - and not every programmer likes that. See this example: sites.google.com/site/unclebobconsultingllc/…
    – Doc Brown
    May 3, 2013 at 10:03
  • If you were applying SRP by anticipating reasons to change, in your example I could still argue it has more than a single reason change. The business could decide they no longer wanted to format a document and then later decide they wanted it to be emailed instead of printed. EDIT: Just read the link, and while I don't particularly like the end result, 'Extract till you just can’t extract any more' makes much more sense and is less ambiguous than 'only one reason to change'. Not very pragmatic though. May 3, 2013 at 10:04
  • 1
    @thecapsaicinkid: see my edit. The main responsibility of the outer function is not to print to a specific device, or to format the document - it is to integrate the printing workflow. And when this workflow changes, that is the one-and-only reason why the function will change
    – Doc Brown
    May 3, 2013 at 11:42
  • Your comment about sticking to the right level of abstraction seems to be what I was missing. An example, I have a class which I would describe as 'Creates data structures from a JSON array'. Sounds like a single responsibility to me. Loops through the objects in a JSON array and maps them into POJOs. If I stick to the same level of abstraction as my description, it's hard to argue it has more than one reason to change i.e 'How JSON maps to the object'. Being less abstract I could argue it has more than one reason e.g how I map date fields changes, how numeric values are mapped to days etc. May 9, 2013 at 9:35
7

I think you're misunderstanding SRP.

The single reason for change is NOT about changing the code but about what your code does.

0
3

I think the definition of SRP as "having one reason to change" is misleading for exactly this reason. Take it exactly at face value: the Single Responsibility Principle says a class or function should have exactly one responsibility. Having only one reason to change is a side effect of only doing one thing to begin with. There's no reason you can't at least make an effort toward single responsibility in your code without knowing anything about how it might change in the future.

One of the best clues for this sort of thing is when you're choosing class or function names. If it isn't immediately apparent what the class should be named, or the name is particularly long/complex, or the name uses generic terms like "manager" or "utility", then it's probably violating SRP. Similarly, when documenting the API, it should become rapidly apparent if you're violating SRP based on what functionality you're describing.

There are, of course, nuances to SRP that you can't know until later on in the project - what seemed like a single responsibility turned out to be two or three. These are cases where you'll have to refactor to implement SRP. But that doesn't mean that SRP should be disregarded until you get a change request; that defeats the purpose of SRP!

To speak directly to your example, consider documenting your print method. If you'd say "this method formats the data for printing and sends it to the printer," that and is what gets you: that's not a single responsibility, that's two responsibilities: formatting and sending to the printer. If you recognize this and split them up into two functions/classes, then when your change requests came along, you'd already have only one reason for each section to change.

3

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.

I have so many times shot myself in the foot by spending too much time adapting the code to accommodating those changes. Instead of just printing the damn stupid PDF.

Refactor To Reduce Code

The single use pattern can create code bloat. Where packages are polluted with small specific classes that create a garbage pile of code that doesn't make sense individually. You have to open dozens of source files just to understand how it gets to the printing part. On top of that there can be hundreds if not thousands of lines of code that are in place just to execute 10 lines of code that does the actual printing.

Create A Bullseye

The single use pattern was intended to reduce source code and improve code reuse. It was meant to create specialization and specific implementations. A sort of bullseye in the source code for you to go to specific tasks. When there was a problem with printing you knew exactly where to go to fix it.

Single Use Does Not Mean Ambiguous Fracturing

Yes, you have code that already prints a document. Yes, you must now change the code to also print PDFs. Yes, you must now change the formatting of the document.

Are you sure the usage has changed significantly?

If refactoring causes sections of the source code to become overly generalized. To the point that the original intent of printing stuff is no longer explicit, then you have created ambiguous fracturing in the source code.

Will the new guy be able to figure this out quickly?

Always maintain your source code in the easiest to understand organization.

Don't Be A WatchMaker

Far too many times I've seen developers put on an eyepiece, and focus on the small details to the point that no one else could put the pieces back together again should it fall apart.

enter image description here

2

A reason for change is, ultimately, a change in specification or information about environment where the application runs. So a single responsibility principle is telling you to write each component (class, function, module, service ...) so that it needs to consider as little of the specification and of the execution environment as possible.

Since you know the specification and environment when you write the component, you can apply the principle.

If you consider the example of code that prints a document. You should consider whether you can define the layout template without considering the document will end up in PDF. You can, so SRP is telling you you should.

Of course YAGNI is telling you you shouldn't. You need to find balance between the design principles.

2

Flup is headed in the right direction. The "single responsibility principle" originally applied to procedures. For example, Dennis Ritchie would say that a function should do one thing and do it well. Then, in C++, Bjarne Stroustrup would say that a class should do one thing and do it well.

Notice that, except as rules of thumb, these two formally have little or nothing to do with each other. They cater only to what is convenient to express in the programming language. Well, that's something. But it's quite a different story than what flup is driving at.

Modern (i.e., agile and DDD) implementations focus more on what's important to the business than on what the programming language can express. The surprising part is that programming languages haven't caught up yet. Old FORTRAN-like languages capture responsibilities that fit the major conceptual models of the time: the processes one applied to each card as it went through the card reader, or (as in C) the processing that accompanied each interrupt. Then came ADT languages, which had matured to the point of capturing what the DDD people would later re-invent as being important (though Jim Neighbors had most of this figured out, published, and in use by 1968): what today we call classes. (They're NOT modules.)

This step was less an evolution than a pendulum swing. As the pendulum swung to data we lost the use case modeling inherent in FORTRAN. That's fine when your primary focus involves the data or the shapes on a screen. It's a great model for programs like PowerPoint, or at least for its simple operations.

What got lost is system responsibilities. We don't sell the elements of DDD. And we don't well class methods. We sell system responsibilities. At some level, you need to design your system around the single responsibility principle.

So if you look at people like Rebecca Wirfs-Brock, or me, who used to talk about class methods, we're now talking in terms of use cases. That's what we sell. Those are the system operations. A use case should have a single responsibility. A use case is rarely an architectural unit. But everyone was trying to pretend it was. Witness the SOA people, for example.

This is why I'm excited about Trygve Reenskaug's DCI architecture — which is what's described in the Lean Architecture book above. It finally gives some real stature to what used to be an arbitrary and mystical obeissance to "single responsibility" — as one finds in most of the argumentation above. That stature relates to human mental models: end users first AND programmers second. It relates to business concerns. And, almost by happenstance, it encapsulates change as flup challenges us.

The single-responsibility principle as we know it is either a dinosaur left over from its days of origin or a hobby horse that we use as a substitute for understanding. You need to leave a few of these hobby horses behind to do great software. And that requires thinking out of the box. Keeping things simple and easy to understand works only when the problem is simple and easy to understand. I'm not terribly interested in those solutions: they're not typical, and it's not where the challenge lies.

3
  • 2
    When reading what you wrote, somewhere along the way I just totally lost sight of what you are talking about. Good answers don't treat the question as the starting point of a ramble in the woods, but rather as a definite theme to link all the writing to. May 5, 2013 at 16:18
  • 1
    Ah, you're one of those, like one of my old managers. "We don't want to understand it: we want to improve it!" The key thematic issue here is one of principle: that's the "P" in "SRP." Perhaps I would have directly answered the question if it were the right question: it wasn't. You can take that up with who ever posed the question.
    – Cope
    May 9, 2013 at 20:20
  • There's a good answered buried in here somewhere. I think...
    – RubberDuck
    Sep 5, 2015 at 1:09
0

Yes, the Single-Responsibility-Principle should be applied to new code.

But! What is a responsibility?

Is "prints a report a responsibility"? The answer, I believe is "Maybe.".

Let's try to use the definition of SRP as "having only a single reason to change".

Suppose you have a function that prints reports. If you have two changes:

  1. change that function because your report needs to have a black background
  2. change that function because you need to print to pdf

Then the first change is "change the report style" the other is "change the report output format" and now you should put them in two different functions because these are different things.

But if your second change would have been:

2b. change that function because your report needs a different font

I would say both changes are "change the report style" and they can stay in one function.

So where does that leave us? As usual, you should try to keep things simple and easy to understand. If changing the background color means 20 lines of code and changing the font means 20 lines of code, make it two functions again. If it is one line each, keep it in one.

0

When you are designing a new system, it is wise to consider the kind of changes you may have to make during its lifetime and how expensive those will be given the architecture you are putting into place. Splitting your system into modules is an expensive decision to get wrong.

A good source of information is the mental model in the head of the business' domain experts. Take the example of the document, the formatting, and the pdf. The domain experts will likely tell you they format their letters using document templates. Either on stationary or in Word or whatever. You can retrieve this information before you start coding and use it in your design.

A great read about these things: Lean Architecture by Coplien

0

"Print" is very much like "view" in MVC. Anyone who understands the basics of objects would understand that.

It is a system responsibility. It is implemented as a mechanism — MVC — that involves a printer (the View), the thing being printed (the Module) and the printer request and options (from the Controller).

Trying to localize this as a class or module responsibility is asinine and reflects 30-year-old thinking. We've learned a lot since then, and it's amply evidenced in the literature and in the code of mature programmers.

0

Isn't it a better idea to only really start to apply SRP when requests to change the code start coming in?

Ideally, you'll already have a good idea what the responsibilities of the various parts of the code are. Split into responsibilities according to your first instincts, possibly taking into account what the libraries that you are using want to do (delegating a task, a responsibility, to a library is usually a great thing to do, provided that library can actually do the task). Then, refine your understanding of the responsibilities according to the changing requirements. The better you understand the system initially, the less you need to fundamentally change responsibility assignments (though sometimes you discover that a responsibility is best split into sub-responsibilities).

Not that you should spend a long time worrying about it. A key feature of code is that it can be changed later, you don't have to get it completely right the first time. Just try to get better over time at learning what sort of shape responsibilities have so that you can make fewer mistakes in the future.

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.

This is strictly an indication that the overall responsibility — “printing” the code — has sub-responsibilities, and should be split into pieces. That's not a violation of SRP per se but rather an indication that partitioning (perhaps into “formatting” and “rendering” sub-tasks) is probably required. Can you describe those responsibilities clearly so that you can understand what is going on within the sub-tasks without looking at their implementation? If you can, they are likely to be reasonable splits.

It might also be clearer if we look at a simple real example. Let us consider the sort() utility method in java.util.Arrays. What does it do? It sorts an array, and that's all it does. It doesn't print the elements out, it doesn't find the most morally-fit member, it doesn't whistle Dixie. It just sorts an array. You don't have to know how either. Sorting is that method's only responsibility. (In fact, there are many sorting methods in Java for somewhat ugly technical reasons to do with primitive types; you don't have to pay any attention to that though, since they all have equivalent responsibilities.)

Make your methods, your classes, your modules, make them have such a clearly designated rôle in life. It keeps the amount that you have to understand at once down, and that in turn is what allows you to handle designing and maintaining a large system.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.