I saw a tweet by @jonskeet the other day which caught my eye:
(I know that doubting things like OCP is pretty close to heresy, but it's just *never* made sense to me.)— Jon Skeet (@jonskeet) March 8, 2013
Well okay then
Well, obviously I kinda agree with this sentiment if you go and hit up the "Immature" 43 year old Uncle Bob's statement which is as written:
They are “Closed for Modiﬁcation”. The source code of such a module is inviolate. No one is allowed to make source code changes to it.
But this got me thinking more widely on my relationship with SOLID as a whole and how that has changed over the years. It reminded me how many times (like the GoF patterns) I've seen an over-zealous and misunderstanding of these concepts wreak havoc in codebases.
I've been able to quote the "rules" from SOLID word for word this past half-decade quite easily, but my relationship and understanding of how these seemingly innocuous statements impact my code has changed over time much like my relationship and understanding of TDD
So, for the next 5 entries, I will jot down my current relationship with SOLID without too much thought or proof-reading (Okay, I lied, I got a few people to read these because I wanted to avoid pedantry - thanks folk)
In object-oriented programming, the single responsibility principle states that every class should have a single responsibility, and that responsibility should be entirely encapsulated by the class.
This is the kind of statement that leads to this sort of mess,
"but omg my controller should only be routing and my service should only be servicing and my data layer should only be data-ing."
And this is unfair, and yet the confusion continues because people see several parts to the whole idea that
a class should only have one reason to change
a class should only have one responsibility
The problem is that most of the time the abstractions people come up with to limit a classes responsibility are horizontal in nature. The true power of single responsibility however, lies in the vertical.
Perhaps this is because they're easier to conceptualise, and easy to write frameworks and patterns for so we can feel productive in our day job - but this is really not as it should be.
What do I mean by horizontal? "I'm going to have a layer of controllers, a layer of validation, a layer of services, a layer of business logic,and a layer of data access". "If I put it behind an interface then I'll be alright".
What do I mean by vertical? "I'm going to write a feature to to manage the availability of books in this library"
Frameworks like ASP.NET MVC don't help us with this and their by-default grouping of horizontal concerns across a project, it makes it too easy to carry on grouping the horizontal concerns across a large project and pretend we have a nice clean architecture because we have folders to put things on.
Your relationship with state
A lot of the time it boils down to state, OO and state have a bit of a confusing relationship, and most of our efforts should be devoted to minimising the amount of mutable state that is exposed to concerns that aren't directly related to each other.
Funnily enough, despite the confusion this is actually pretty easy to conceptualise via tooling and metrics, if your classes are cohesive, most of the methods on that class will touch most of the state in that object.
Having a horizontal layer whose responsibility is "modifying state in the database" is nonsensical and vague.
Having several objects whose responsibility is looking after the state for a particular feature and then persisting it (perhaps via another facillitiating object) has a lot more sense in terms of understandability and traceability.
A note note on orthoganal concerns
State based data persistence is not (usually) an orthogonal concern, neither is the workflow/routing sat in front of your MVC application - logging on the other hand can be, and authentication/authorisation can be too.
Clearly, you shouldn't be constantly modifying these vertical slices because of a change to your authentication scheme or logging system. Trying to classify too many things as being orthogonal to your core functionality however is what leads to layer soup, and care should always be taken not to do this.
You can discover these as you go, there is nothing wrong with littering your code with these concerns to begin with, and then as things get repetitive, pulling them out to make life easier. Premature attempts at trying to isolate these concerns is often the path to layer soup.
Upwards from the class level
Trying to make classes whose concerns are limited, whose reason to change are limited is all very well and good, but it's no good looking at things under a microscope and saying Hey, things look okay, when you sit back and have to go what is this crap?
Let me invoke the NodeConf drinking game, a lot of the time it is much more valuable to think of your codebase as a collection of modules which are independently versioned and have clear boundaries set up between them.
Any of these small modules can start off by being a complete and utter mess, and if further work is required in that area you can either re-write the module in a few hours, or refactor it as you go (did you say spike and stabilise again Rob? I think I did)
That is where single responsibility really means something, and you can save a lot of time in development by building these disposable building blocks when you're rapidly iterating on a product.
I seem to have started with the least controversial and one of the most harmful of rules, oh well...
Thus ends my brain dump on responsibility and the many routes to layer soup. Tomorrow I'll go the heart of the matter on OCP, and then wind down the rest of the week through the rest of the set.
2020 © Rob Ashton. ALL Rights Reserved.