This is part 2 of my series in which I largely whinge that complex problems wrapped in simple terminology are, in fact, complex. Part one can be found here.
I delayed writing this post for a week or so, as I was struggling with the idea that I was discussing something in isolation, SRP, that really needs to be looked at in collaboration with the rest of its brethen, namely the OLID. Eventually, however, I decided not to delete the last post and carry on with another topic, largely as SRP is often flirted with in isolation.
Why this is, I think, as I mentioned in my last post, is it appears the most accessible and obvious. Skimming through a page on SOLID you see a paragraph discussing the statement `A class should have only one reason to change`, and a paragraph discussing what the meaning of `Liskov Substitution Principle` is.
The path of least resistance is, frankly, the paragraph made up of words you understand.
In my experience, the first step a developer, new to SRP, will take is to (hopefully) recognise their own sins.
I can think of many projects I have worked on in the past, with classes (probably named something like
BoopController or, my favourite,
BoopDatabaseAdapter) who have grown 1000s of lines long. Usually they started well, but over time, like the best of us, they grew old and ugly.
Generally the code is 80% good, 15% passable, and 5% hideous – but, at runtime, a
Boop pretty well, occasionally something will happen, a sneaky
InvalidBoopStateException, maybe, but management and customers are on your back to get the task done – and Multi-K length files are just an unfortunate side effect.
After a while, however, these classes often become the bane of your working life. They are large and, by nature, end up integral to your logic. They keep popping up like an unsatisfying simile, and just keep getting more complex, buggy and frustrating the more you try and fix them.
And, in truth, they are many times the cause of that oft-given thought that starting over again, from scratch, maybe a good idea. Debugging is hell, squashed problems cause new problems to pop up elsewhere. Modifications are a nightmare, and often devolve into a forest of copy/paste code enclosed by customer specific if statements.
If you are feeling courageous and clever, you may try to solve your problems with polymorphism, and, to paraphrase the regex joke, now you have two problems. To be fair, I’m not part of the ‘polymorphism is the devil’ camp. It does have its place, but that place is not here.
So, waving your newly discovered SRP banner, and depending on your time you either
- Start hacking apart your old code
- Determine to better next time
Now, this is a good thing. Despite the mistakes you’re inevitably going to make, it’s a first step. Your future is ahead of you, and you can be clean.
The first problem you’ll probably face, however, is this. Without careful planning, you’ll just start to build a file structure that mirrors your enormous, unorganised, classes of yore.
Worse still, you’ll start to struggle with all those dependencies in your code, the dependencies that made your working life an uphill slog in the first place. Your original class probably had quite a few global variables, or maybe method scope variables that were being passed liberally between a gamut of private methods.
Unfortunately, in isolation, SRP does nothing to resolve this spaghetti.
public Boop BuildNewBoop() out to its own
BoopFactory could reveal a number of dependent methods. So, keeping on track, we split these dependent methods out to their own classes – but almost certainly there some other methods inside
BoopManager that also rely on these methods. That’s fine, we can move that method to some Utility Class (I’ll discuss utility classes traps in the next post), but what of those internal or global variables and objects?
From here it is way too easy to to build your new Singly Responsible classes with constructors or methods with multiple, complex, parameter lists. True, named and optional parameters have made this less of an issue, but letting parameters grow like weeds can be very confusing to other members of your team, not to mention to you when you’re trying to make sense of your code months later.
One way to get out of the dependent parameter hell problem is creating a Context object to pass around, that can hold all required data for all called methods, and just add the use the ones needed at the time. A context object, however, is very much a carpet to sweep the problem under, it’s just a list of parameters hidden in a class, and one you’re going to have to know how to build.
Now at this point you could build multiple context objects to fit each requirement, and use a context factory pattern to build the context you want for each given call, but if you’re doing that, you may as well just go back to building EJBs ;p
At this point, we have to admit, the code is just as much of a tangled dependent mess as it was in the first place.
We have done nothing to fix the fundamental issue. The issue was not that you had lots of great code in one file that just needed demarkation, it was that, having all your code in one file, it was easier to build bad code than well structured code.
So, what’s the answer? The answer is, in part, to restructure your code to remove the dependencies from being baked in before you even start worrying about SRP, and I’m afraid it’s not simple.
SRP is only a part of it, SOLID is a great starting point, but in the end it’s just good ol’ hard won experience. It’s also failing, learning lessons, researching and improving. So write go ahead, bad SRP, and learn from your mistakes, but please don’t think Single Responsibility on its own as your panacea.
It’s pretty tempting to conclude here, don’t look at Single Responsibility Principle as a tool in isolation.
It’s short, it’s simple, it’s pithy. It’s also tempting because I could avoid admitting more of my mistakes (let’s face it, whilst I’ve seen other coders fall for these traps, I’m largely talking from personal experience), but maybe from a sense of introspective Schadenfreude, and personal growth, it’s probably important I slog on.
And, most of all, it doesn’t fully really address the title of this series. SRP is hard, and complex dependencies are only part of it.
To be continued …