I don't love the single responsibility principle
sklivvz.com
sklivvz.com
The TCP/IP stack exemple in the Wikipedia article linked below hints at SoC pretty well in my opinion. One layer allow the layer above to disregard details about the physical link. The next layer makes cross-network addressing transparent. The next one allows apps to disregard packet ordering and loss, and so on. Each layer solves a problem for the one above. Yet one can have multiple responsibilities. This is all about cognitive load.
A system described in code is nothing more than a proposed consensus around tools, names, boundaries, paradigms to be used to talk and think about a solution to a problem. The ultimate measure of success is this: our ability to tame the binary beast. If "change" plays a role (it sure does) it's probably not the main or only metric. Well ... when you're not selling "ability to change" consulting and books to software companies, this goes without saying.
The reason you do this is to manage cognitive load in humans, not "coupling" in computers.
First, we want to establish the idea that a computer language is not just a way of getting a computer to perform operations but rather that it is a novel formal medium for expressing ideas about methodology. Thus, programs must be written for people to read, and only incidentally for machines to execute. Second, we believe that the essential material to be addressed by a subject at this level is not the syntax of particular programming-language constructs, nor clever algorithms for computing particular functions efficiently, nor even the mathematical analysis of algorithms and the foundations of computing, but rather the techniques used to control the intellectual complexity of large software systems. [1]
[1] http://mitpress.mit.edu/sicp/full-text/book/book-Z-H-7.html
I have to wonder if part of the reason there's so much bickering about this concept is that a lot of apps aren't a complex problem domain, but rather they just drown slowly in incidental complexity and feature creep.
I find the author's alternative much more useful in practice.
Absolutely agree. Proponents of approaches like this tend to only worry about intra-object complexity, and ignore the fact that a vast, complicated object graph is also hard to reason about.
That reminds me of this, not sure if it's satirical or not: http://www.antiifcampaign.com/
Basically it's advocating removing if/switch statements and replacing them with polymorphic method calls. I understand that polymorphism has its value, but think that it's only valuable when, for lack of a better phrase, the thing that's being polymorphosed is "big and varied" enough that it makes sense to impose this extra level of indirection. I think that it being hard to explain when something is worth it, is enough justification that principles shouldn't be arbitrarily decided based on that.
http://c2.com/cgi/wiki/wiki?PolymorphismVsSelectionIdiom
(Disclaimer. I wrote the top of that wiki page in a former life.)
I think that's precisely what I was trying to say - polymorphism makes sense when you already have a bunch of classes to do it with, and classes which already contain lots of other fields and methods; it doesn't make sense to create a bunch of classes just to use polymorphism.
If you have cascading ifs, there is a good chance there is a huge set of ifs for every place this type system is missing. Meaning, if you wanted to add another "case" to a feature, you are modifying cascading ifs in 5-10 places, not even just one.
Wrapping up all of this code into an implementation of an interface, that is "hooked in" at each contact point allows you to add a cohesive new use case by generating a new implementation of an interface, instead of "forgetting 2 places in the code" and causing massive bugs or issues because of it.
It of course, also makes it easier to test!
They tend to recommend solutions that are highly dependent on the problem to be solved.
Trying it the other way round, i.e. fitting the problem to an idealized solution, rarely works, and I find this is what I see a lot when I see people who place an emphasis on being "object-oriented" as opposed to just solving the problem with minimum redundancy but not twisting their code to do it.
As an example, creating a function and calling it twice from two similar classes is much easier to read than inventing an intermediate class that is an ancestor of the two (something I see very often from the hardcore OO folks).
You really see this a lot? As far as I can remember using inheritance over composition for de-duplicating code has been considered bad practice in OO circles for at least 15 years.
I call those "half responsibility classes", where classes don't actually have any responsibility other that carrying the little extra information the parent needs to perform the actual job.
That's bad, because classes are a way to encapsulate data and logic; and those... _things_ don't have any logic.
That's terrible OOP right there. I don't think the SRP encourages such thinking.
I think the SRP is a subtle and often poorly explained thing that is very easy to misunderstand in a way which encourages such thinking, even though understood properly, it does not. The basic problem is getting the right "level" for the responsibility in context, and that's not an easy thing to explain how to do.
Stating that "binding business rules to persistence is asking for trouble" is flatly wrong. Au contraire, It's the simplest thing to do, and in most cases any other solution is just adding complexity without justification.
I don't feel that increasing the class count necessarily increases complexity, nor do I feel that putting several things into one class reduces it. A dozen components with simple interactions is a simpler system than a single component with which clients have a complex relationship. My views align more closely with those expressed [1] by Rich Hickey in Simple Made Easy.
Classes as namespaces for pure functions can be structured in any way; they don't have any tangible affect on complexity. "Coupling" is irrelevant if the classes are all just namespaces for pure functions. I also find that most data can be plain old data objects with no hidden state and no attached behavior. If most of your code base is pure functions and plain data, the amount of complexity will be fairly small. As for the rest, I think that the author's example of maximizing cohesion and the SRP are functionally identical. They both recommend splitting up classes based on responsibility, spatial, temporal coupling, or whatever other metric you want to use. Personally I prefer reducing the mingling of state, but I think they're many roads to the same place. Gary Bernhardt's talk Boundaries[2] covers this pretty well.
I dislike having a class/module per file.
Where I definitely do prefer Django in this regard is that models declare their data fields, rather than them being in a completely different part of the source as in AR (not Mongoid, I now realise). Do I remember the exact spelling I gave to every column when I migrated them months ago? No. It's good to be able to see it all in one place rather than having an extra tab to cycle through. I don't see any practical benefit from decoupling here.
I also like that in Django, you declare the fields on the models first and then create the db migrations from them, rather than writing a db migration first to determine what fields the models have.
South/1.7 migrations is definitely the best way of the two to manage that coupling. Rails's charms lie elsewhere.
Meanwhile a lot of the practically minded developers like DHH just accept that their objects will be tightly coupled to the database and just deal with it, claiming that anything else would be adding unnecessary layers of indirection.
I am pretty new to Django, but I get the impression that it's not so hard to just not put your business logic in models.py, and put it in separate classes of plain old python objects instead. Maybe that's why I haven't heard about this debate playing out in the Django community the way it is in the RoR community...
If I'm in extreme mode I take the view that each file should be a single screen. That means a tangible reduction in the complexity of working on them (no more scrolling - each class is just in its own tab).
I've used IDE's that make this more or less painful, but none that actually solved it. If anyone has any suggestions on one that does, I'd be interested to try it out. I don't really care what language. I can pick up enough to see what it feels like.
I also want to say that rich hickey talked about a file as a unit of code not being very good, but I don't recall where, or if he really said it. I want to say it was in a Datomic podcast right around when details about it were coming out.
http://thinkrelevance.com/blog/2012/10/12/rich-hickey-podcas...
He talks about his definition of "simple" (by digging into what the original English definition was) and what that means for code.
The future is irrelevant? That's exactly what design is for -- to protect you from the future. If you didn't care about the future there would be no need to design at all.
A good way to evaluate various candidate designs is to imagine future use cases and ask which design holds up better. A good design will respond really well (ie., require fewer modifications) to novel use cases. Many people think this means design starting with future use cases. But you really just need to design with good principles. Future use cases are useful however at objectively evaluating designs and resolving arguments, such as Which design is better for futures that we care about?
>> Stating that "binding business rules to persistence is asking for trouble" is flatly wrong. Au contraire, It's the simplest thing to do, and in most cases any other solution is just adding complexity without justification.
Everything is a tradeoff. It doesn't make sense for many cases (hacking, etc) to avoid the simplest thing. But if you are building a project that's longed-lived, I can give you countless examples of how coupling business rules to model objects resulted in pain and copious paper cuts.
For me, I'll always decouple them. Why? Because it costs me nothing extra to do it and I have internalized the value in this principle from experience.
>>> Not all applications are big enterprise-y behemoths that benefit from Perfect 100% Decoupling™
You're right. It depends on how much you care about your future. Do you want your codebase to respond with agility to the future. Or are you okay with increasing your technical debt?
>>> Therefore, classes should be: >> small enough to lower coupling, but >> large enough to maximize cohesion.
Not sure I understand. Author seems to suggest these two are in contention with each other.
>>> Coding is hard and principles should not take the place of thinking.
Absolutely. There are many cases where the rules are to be broken. Systems with too much flexibility are just as bad as monolithic ones. It's an art what we do.
As for coupling and cohesion: if modules are too small, they usually lack cohesion ; if module are too big, they usually have high internal coupling. The sweet spot is in the middle. And you can create low-cohesion, high-coupling modules if you want :-)
Most of the big design philosophies are around "building for the future", but the truth is that we're almost always wrong about it. And thus, most of time, the code needs to be rewritten. And a simpler, straightforward code is much more easier to rewrite or refactor.
There's a third stage, though: using classes without inheritance, but with interface/protocol declarations. This is made much easier in the latest generation of languages (Go, Rust, Elixir) that infer the interfaces/protocols which a given piece of data supports, allowing you to use interfaces/protocols without classes.
OCaml has had this feature since 1996.
For example, with-redefs. This allows you to replace a given function anywhere in the whole codebase with any lambda. This rebinding only lasts inside the scope of the s-expression. When I realized that, I realized everything I knew about DI and interfaces were just hacks of incredible complexity to get around a poor language that wasn't built to allow unit testing. I now think it's impossible to have both unit testing and a good design in Java/C#. Neither were made to be easy to unit test, it wasn't even a concept when Java was made.
In C#'s defense, there is a way to mock a static function by replacing it using the Mocks library. To MS's shame, that library requires the 13k edition of Visual Studio. When I realized that, I realized I want to leave C# for good. I'm not interested anymore in a language that resists testing so much I have to choose between an easy to understand design and unit tests.
It's a common misconception that interfaces are the only way to do polymorphism, because that's usually the only way in Java.
I try very hard not to get attached to code too much, refactor agressively and will throw it out when I feel it needs redoing. For larger codebases I tend to rip out a chunk between a pair of interfaces and tackle that section indpendently. I'll change either the code or the interfaces but never both in the same sitting.
What the blog post is about is more of a: "I do things this way". Now I'm on a team and my team mate wants to do things a different way. Instead of realizing I'm on a team and it is matter of balancing abilities to make a cohesive software product, I'm going to bicker about why my team mate is wrong. And I'm going to write a big blog post about it and look at SRP in isolation instead of applying the same cohesive viewpoint (that I claim my team mate is missing) to understanding SRP.
In the more modern reading of the open/closed principle if you have multiple different variants on the behavior of a thing, you compose those variants in via an abstract interface. Then as you need to add even more variants you needn't change the original code any more only introduce more concrete implementations of the abstract interface that you compose at instantiation time as necessary. This approach is especially useful as your variant behavior grows. That is, a single boolean switch is probably easier to reason about than 2 implementations on an arbitrary interface. But once you get to 3 it becomes less obvious which is better. Any more than that and I usually reach for an interface without too much thinking about it.
So no thanks, I don't want Uncle Bobby to save me, I don't need an encyclopedia either. Have a good day my friend. (Go easy on the blue pill ...)
That is the SRP.
Example: A class that analyzes a data stream and prints a report. The data analysis will interest one group of people. The format of the report will interest another, different, group. The first group will ask for changes to the algorithms. The second will ask for changes to the format. The principle says to separate those two concerns.
This goes all the way back to David Parnas and the separation of concerns. His papers that describe it are freely available on the web. I suggest that they be studied, because they are full of wisdom and insight.
>SRP is very simple...different people want to change a class for ... different reasons...
Back in October 2009, in https://sites.google.com/site/unclebobconsultingllc/getting-... I asked you a question on the SRP and you replied as follows:
"SRP says to keep together things that change for the same reason, and separate things that change for different reasons. Divergent change occurs when you group together things that change for different reasons. Shotgun surgery happens when you keep apart those things that change for the same reason. So, SRP is about both Divergent Change and Shotgun Surgery. Failure to follow SRP leads to both symptoms."
Has the avoidance of Shotgun Surgery taken a back seat?
Philip
So what if the first object ensures that it's always in the right state to perform it's behavior? Well, now it's observationally equivalent to a state-free actor. Why did you involve state in the first place?
Because languages make it hard to not do so?
The OP asks "but why should a class have one single Reason To Change™?" Answer is in the text - "If a class has more than one responsibility, then the responsibilities become coupled. Changes to one responsibility may impair or inhibit the class’ ability to meet the others. This kind of coupling leads to fragile designs that break in unexpected ways when changed."
The case the OP makes for mixing Business logic and Persistance is really ironic too. In thinking he's arguing \against\ SRP, the poor example he gives actually argues \for\ it. Yes, validation and persistance usually conceptually go together as one responsibility - storing your data. Calculating payroll (see PDF), is a whole different responsibility, better suited for a separate abstraction. So the strawman argument is not really in the PDF.
The book mentions the "Unbalanced" counterpoint already. See: "If, on the other hand, the application is not changing in ways that cause the the two responsibilities to change at differen times, then there is no need to separate them. Indeed, separating them would smell of Needless Complexity. There is a corrolary here. An axis of change is only an axis of change if the changes actually occurr. It is not wise to apply the SRP, or any other principle for that matter, if there is no symptom."
Now for the amazing TLDR of the entire piece, it goes something like this: SRP is not a clear and fundamentally objective design principle, so let me offer another principle huff huff but dont worry its not clear cut (so dont follow it?!)!
This doesn't lead anywhere. Talking to the co-worker more might have been better than adopting a defensive attitude.
The big lesson here is, learn concepts like Cohesion vs Coupling, SRP, design patterns, to give you a common vocabulary, but keep your mind open and try to write code that is well organized and is free of code smells. If anybody has suggestions on how things can be improved, measure their suggestion on its merits. Software development is an excercise in balancing all kinds of different types of concerns - pun intended :). Sit down and list pros and cons between alternative approaches, and let the design that best addresses the problem/constraints at hand win.
And even better, next time, if you want to save yourself some trouble, ask others for their opinions and share your concerns with the code before you even write a line of code. Then nobody will get 'religious' in a code review and it will allow a team to focus on results.
Designating a class as having a single responsibility is an easy way to ensure that there is a minimal amount of state per class, but if the abstractions you are creating with this principle aren't logical than you are still left with a lot of moving parts someone has to think about when making modifications.
All these principles mostly only work if the system is also being designed rationally. Applying principles arbitrarily to a messy codebase will not necessarily get you a good design (and probably not) but thinking about these principles when making design decisions can sometimes be useful.
All the examples I see are one-way towards simply creating a million single method classes.
That's a phenomenon that I've definitely seen a lot; often with the accompanying obfuscation that the method bodies have only one or two statements in them, that just calls into some other methods. It may look like it's made the code simpler locally, but all it's done is spread the complexity out over a wider area and increased it. This isn't "well-designed" or "straightforward", it's almost intentional obfuscation. I've seen this effect most with programmers who were taught OO design principles very very early, possibly before they even had a good grasp of concepts like procedures, loops, and conditionals. "When all you have are classes, everything turns into an object."
What's the issue?
> A good, valid principle must be clear and fundamentally objective. It should be a way of sorting solutions, or to compare solutions.
Okay, I'm listening. What is your alternative?
> It's not a clear-cut principle: it does not tell you how to code. It is purposefully not prescriptive. Coding is hard and principles should not take the place of thinking.
And.... we're right back to subjective and general again. Set up a straw man only to knock it down with an identical straw man.
Of course, reducing coupling and raising cohesion makes the class responsible for less and less... So are we back at the authors interpretation of the SRP? Seems like it to me.
That said this entire article is essentially arguing FOR the SRP. The whole point of the SRP (in conjunction with the rest of the SOLID principles) is to decrease coupling and increase cohesion. In his refactor of class C we have an example in the first case of a class that violates the SRP and an example in the second of 2 classes that follow it. Excellent, the author and Uncle Bob are both happy.
But what really bothers me about this article is the following: "Furthermore, there is no reason to separate "business" logic from "persistence" logic, in the general case. The large majority of Employee classes that people need to write are likely to only contain fields and maybe some validation -- in addition to persistence."
A) Please don't tell me what the large majority of things people need to write are. You cannot possibly actually prove that assertion. In my experience having a 1 to 1 relationship with a class and a database table is a sign of either a very simple problem space, or a very poor design.
B) If you do happen to work in a problem space and are finding yourself writing something that is a collection of fields, some validation and some persistence, that is not an example of a violation of the SRP, it is an example of you needing an EmployeeRecord and not an Employee. The difference is simple, an Employee has complicated business logic in it and a record does not.
This seems to be the central debate currently around Uncle Bob's tactics. Lots of people seem to be writing PVC (Persistence-View-Controller) applications and thinking they are writing MVC (Model-View-Controller) applications. It then seems to be overkill to split out the persistence layer from the "model" layer because there isn't much difference. If there isn't much difference you didn't violate the SRP! Your Single Responsibility is persisting some data! Your design is fine. Move on with your life. But on the other hand, if you find your self writing complicated business rules that are hard to test because of the wiring required for your persistence, maybe you've violated SRP and should split them up a bit.
The example of the client needing to instantiate B to pass it to A's constructor seems like a poor example of tight coupling. The only reason to instantiate B is because A needs it. The client doesn't literally need to know about B, it simply needs to know how to construct/build A. This could be done through a factory or service locator, or it could be done by A having B autowired into it, which would free the client from having to instantiate B directly.
I do, however, agree that SRP is poorly defined in most of the resources you find, so if anyone has links of case studies of applying it properly, they'd be interesting to review.
This is incorrect. The SRP is a dependency management principle. It has nothing to do with the size of classes (however that might be measured). The goal of this principle, like the rest of the SOLID principles, is to minimize the impact of changes.
Hence a bug fix does not constitute a "change" since it does not change the responsibility of the class, neither does a refactor for code clarity or performance improvements. The purpose/responsibility of "what" the class does, does not change when you do any of those things.
"sometimes a class with more reasons to change is the simplest thing" Could you give an example of this? I can't think of one instance.
"Stating that "binding business rules to persistence is asking for trouble" is flatly wrong. Au contraire, It's the simplest thing to do, and in most cases any other solution is just adding complexity without justification" Now this statement here is flatly wrong precisely for the reason specified in the Uncle Bob's example. Let's do a thought experiment. Consider that you have an Employee class that is a black box to you. Someone else wrote that code and didn't have any exception handling logic in their code. The Employee class did business logic and persistence control. When you tried to use the class there was an error. Now which part of the class was faulty? The business logic part or the persistence control part? Now consider that you needed to use the same Employee class in another project but it needed a different set of business rules but the same persistence control, you would not be able to use this Employee class you would have to create a new one and duplicate your persistence control code.
Design principles have been developed to make sure that code written is maintainable, extensible, clear, comprehensible. Sure me as a single developer who is working on a simple project, who knows what each of my class does and has a small enough code base such that I don't need to separate the application into layers (e.g. call my database directly from my view because I just need this one value and don't want to write a couple of classes to that) can indeed write classes that have more than 1 responsibility. There's nothing to stop you but it does violate the SRP which will make it tougher for someone else to come in and maintain your code or extend it.
Whichever code is more concise & readable is generally the winner.
(Note: a lack of understanding of algebra / calculus does not make the code 'unreadable', it just means the developer is innumerate)
Maintenance is generally a red herring, it's best to use statistical methods to determine the likelihood of maintenance.
Usually if you're doing lots of maintenance you have other problems in your code / workflow, such as mistaking your codebase for your database.
That is an interesting assumption given that in many problem domains proving that the code works as specified is the hardest problem.
"Maintenance is generally a red herring, it's best to use statistical methods to determine the likelihood of maintenance."
I really dislike anyone who makes general claims about the entirety of software development. I for one spend way more time changing existing code than writing new code. I know that there are problem domains where that is not the case so I don't recommend my methods for those spaces.
What are these statistical methods you are referring to?
"Usually if you're doing lots of maintenance you have other problems in your code / workflow, such as mistaking your codebase for your database."
Or you are working in a problem domain that shifts a lot, or where requirements specification is more expensive than deployment opportunity costs, or in a legacy system.
Actually 'change requests' are even easier when the requirements change frequently, just provide an estimate in excess of when you think the next change will be, then put your feet up and wait for the requirements to change again.
PS. Changing requirements isn't 'maintenance', it's a change request.
PPS. Having to add code to add fields to a form means you spec'd your solution around your forms instead of specing your solution around solving the problem of changing forms. (aka. you baked your problem domain into your code and now you're fucked) (eg. you mistook your codebase for your database)
A change, in the context of SRP is a change of responsibility / functional purpose.
Regarding the granularity to which you should go to with SRP, the idea is that it's not fixed; you change this as feels best for your code base. If you write code without much logic (e.g. a basic CRUD application with minimal validation) it's fine for the classes representing your tables' records to also hold their logic, since at this stage the single responsibility is, say, Employee. Once you start to get something more complex the separation of concerns becomes more important, so you need to break this apart into Employee Record and Employee Validation; perhaps more layers. That may seem contradictory as there are now two classes with two responsibilities where before there was only one class with one and a bit responsibilities - but the key is to be pragmatic. Don't write thousands of lines of code and multiple classes if that introduces complexity with no pay off. However, if you have one class that does too much it'll become hard to maintain; and being aware of the SRP principle will help you work out a sensible way to break that class down into more comfortably manageable chunks.
It's the same as EDD: it's fairly subjective, but it weeds out the extreme cases.
The benefits of SRP are usually a side-effect of applying more objective rules to the code.
DRY is the primary driving force. It pulls shared responsibilities out of modules and leaves no doubt that those responsibilities were shared. It detects recurring concepts and gives them a representation, thereby increasing coupling.
The second force is to reduce access to private code: out of 100 lines of code in your module, how many actually need to access that private concept on line 42 ? If the answer is "30", then what are those other 70 lines doing in this module ?
Apply both forces to a code base, and the SRP will appear out of nowhere.
If one has to write "This class does x and y and sometimes z if w", it's got several responsibilities.
If one can write "This class does x", it's OK.
X can then be arbitrarily complex, so code size has nothing to do with it in my world.
Deleted comment
What?
What about: "The purpose of class is to encapsulate state".
If there is no inner fields in class, there is no reason for class. There is no state.
One or more fields in class means that they make sense when looking at all of them together, as a "state". If some method changes something, the all state encapsulated by class means something else.
It is possible to extrapolate all other guidelines from this basic starting point.
However, the fear of many little objects smells strikes me as backwards. Unix systems are built with and used by many little utilities. As a result, a few simple commands can be strung together to make quick work of interacting with the system. Often, I find that the hardest utilities to master are the ones with the broadest scope. My experiences with large objects are similar.
I'm pretty sure I encountered that particular example of things that shouldn't be coupled in OO design before Rails existed.
1996, and I am pretty sure this marketing speak page reflects what had already been being talked about in the industry for quite some time.
This certainly has a certain kind of pragmatic value in getting something simple functioning quickly, its not just simply bad design of the framework. OTOH, it has the potential to impose a legacy cost as the complexity of the application increases.
when one writes code, there are only real, present requirements. The future is pretty irrelevant
I'd disagree with this. The future of your codebase matters unless you're writing a throwaway prototype. And when you rewrite a codebase N times, you'll discover that there are ways of structuring it so that future tasks will become far easier. E.g. for Lisp you'd refactor common patterns into utility functions, and for C you'd carefully craft the interfaces between modules so that they're unlikely to be used improperly / unlikely to be surprising.
General rule I use: does this block / section of code increase complexity of the overall purpose of the function or class. If it does, it should probably move, likewise if it isn't relevant to the overall functionality / behaviour it should also probably be moved.
Everything is a trade off in the tech world. You can't argue you are right or they are wrong, only that you are right in this instance or that.
Comments are the most interesting part :)