Escaping abstraction hell spirals
You’re probably a lot like me when it comes to design decisions around code.
You like clean code.
It is satisfying to both read and write, your teammates appreciate it, and it makes your application easier to work with down line, so you put a lot of effort into it.
But upon the this purity of intent, the psychological gremlin of perfectionism often rears its head.
Driven by a subconscious shame and fear of failure, it’s becomes the obsession to find the best way to go about your task at hand.
It’s tough not knowing what you don’t know.
Of course, a quick web search to find the best way of doing something is a great thing, and it’s how our software improves, but what has happened to me, and likely you too, is that it becomes a worm hole.
Scanning one browser tab on how to use Mediatr then spawns three more on CQRS, keeping controllers thin, and the repository pattern.
You realize you weren’t following the single responsibility principle and that you have a command inside your query, so you’ll first need to refactor that.
As you refactor and are at the same time drinking beyond your fill from Google’s tit, you come across domain driven design and decide it would be better to host an event storming session with your team before you can feel comfortable implementing the original small task you were assigned.
This is a hell spiral.
It doesn’t end, and rather than finishing your task, you instead just add a bundle of abstractions that in theory reduce coupling, but are likely untimely and low in value to your team and your company.
This is usually because advice articles are written with a very specific problem context in mind which is often not your problem context, so you’re building your application based on the needs of some other application.
It’s also a lot like waterfall vs. agile software development.
The perfectionistic obsession has you putting the cart before the horse trying to predict eternity rather than just finishing your task.
The best architecture promotes agility (ease of change), but if you haven’t had a need to change anything about it, you’re prematurely optimizing based on a hunch.
What to do instead
First, don’t beat yourself up. This sort of architecture thrashing is something just about every developer I’ve ever met has done.
With time and experience, you will be able to make faster and better predictions for what kind of design a certain feature should have, but until then you can use a much simpler approach.
Start with just getting your feature working.
Put aside all of the shoulds the software world is tossing onto your shoulders and just get it working in the simplest way possible.
Perhaps begin with one single method and a set of comments that break the feature up into a high level algorithm.
private void purchaseItem()
{
//set up payment gateway api client
//process payment
//generate receipt
}
After you have it working, commit to maybe 30 minutes to an hour on basic tidying.
Focus this time on making it easy to understand (small methods, clear names), and on aiming for the single responsibility principal with your those methods and their classes.
That’s it.
Move the hell on.
And here is the important bit – you can trust that when your feature begins being used in the wild, the opportunity to iteratively improve the design will reveal itself organically.
Rather than toiling over upfront psychic predictions, your optimal design emerges via actual pain hot spots.
So you end up with a design that is more congruent with need, and you finish your features faster, with more ease and enjoyment.
Going further…
If this resonated with you, I encourage you to take a look at my article on boundaries to deepen your understanding of other personality traits that could be affecting your growth as a developer.