This is turning out to be a series on best practices I willingly break! This time I'll talk about when I intentinally bend the YAGNI code smell.
The YAGNI (You Ain't Gonna Need It) principle is a reaction to a common misstep among eager developers. It can be tempting to plan for the future and account for every possibility when designing a solution. This leads you to build for non-existent requirements, adding features that aren't needed, wasting time and adding complexity. An example of this might be adding an undo feature or introducing configuration files where hardcoding a value would do.
Upon seeing such a feature, it is quite convenient to have a made up word like YAGNI to utter. In code reviews or after an over-the shoulder glance, any teammate can justifiably drop this term and demand a justification for the extravagance. What better way to break the spell of an enraptured coder, bringing them back to the reality of deadlines and client needs.
I have a great deal of respect for this principle, but my interest in concern counting and everything in its right place can win out at times. The cases I'm thinking of are what I call infrastructural concerns.
Infrastructural concerns in my book are things you might find in a library or framework well suited to the task at hand. Things like iterators, event handlers, or workflow managers. They may be very specialized for a problem I'm solving, but if you squint, you'll see a problem that's been solved numerous times. For me, these aspects can ride the edge of what's part of the solution domain, and what's conceptually outside of your app. If I'm ever on the fence, I tend towards the outside.
In these cases, this means I'm writing library code. I'm adding unneeded features, and writing unit tests that cover impossibilities. I'm solving a general problem for what seems like a future that may never come.
I could go to the internet and add another library to our build, but there's a chance it will be buggy or a conceptual leap a bit far for an entire team to take at once. It may represent a different code style, or have an integration cost. What's guaranteed is that this code will be walled off and likely be non-trivial alter to our needs, should the need arise.
This is when I start writing a library of my own, integrating it into the app as I go. My unit tests cover every assumption a fresh set of eyes might bring. Names are as generic and canonical as I can muster, and I write in the style of the greater application. This code will live off to the side in a lib/ or a utils/ but might be subclassed within the app. Compared to pure library development, it's interesting to note that I can still work outside-in since it is being designed for a specific use.
For me the benefit comes from the cognitive clarity you gain. I'm using standard concepts which have a good chance of being familiar, written in the same style as the rest of the app. If I do my job well, it will be taken for granted, and fade to the background as someone else's maintenance free concern.
So there you have it. I'm probably not Gonna Need half of the features I build into this infrastructure, but I think I've gained something in the process. Maybe you disagree? Tell me about it.