Forget YAGNI, Consider YMANI
The opinions stated here are my own, not those of my company.
Google has a number of high-quality products and platforms such as Android and Chrome. They have been engineered to be highly modular with the ability to build new features across teams without impacting their billions of users.
You can see a number of platform capabilities that make engineering easier and more reliable: feature flags, continuous integration, and a modular approach with many integration hooks.
When you look at many open-source programs today, particularly when they’re starting out, you don’t see these. Features are added ad-hoc to production in any place, then they’re tested manually. It results in lower developer productivity and lower release quality.
Without a structure in place, it takes time to get the lay of the land. You need to learn the entirety of the codebase before you can feel confident where to make your change. You need to test thoroughly, manually, to ensure that your change doesn’t cause unintended consequences somewhere else.
Had there been a modular structure, it would be much easier to identify where to make your change and there would be fewer opportunities for a significant bug to occur. Tests could automatically verify your change is reliable before it is submitted. By adding a feature flag to your change, users would be able to opt-out entirely in case there’s a problem that is uncaught.
Why do open-source projects start out with just little infrastructure and engineering design? There is a principle in software engineering called YAGNI, You Ain’t Gonna Need It. There’s another called KISS, Keep It Simple Stupid. A lot of programmers give and receive advice to only focus on what’s in front of you and avoid premature optimization.
But is this the right approach? Perhaps when these ideas were first passed around, it made a lot of sense to build in a simple way. Yet today it seems like an antiquated idea. I would like to make a case for a new approach: YMANI, You Might Actually Need It.
When creating a new software project today, it might only do one thing. It’s straightforward to run, and easy to catch any programming errors. Why would you need feature flags? What automated testing is necessary?
Your product is a success, and you start getting feature requests. You also find bug reports in very niche circumstances. You start adding things in, fixing other things, and you ship products with worse and worse quality. At some point you may realize that your product needs a rearchitecture, once you’ve spent too much going down the path of developing a monolithic and overly complex piece of software.
I get the hesitation to add architectural complexity at the start. You want to hack together a proof-of-concept as quickly as possible. You may have a simple vision in mind and see no reason to add unnecessary cruft to your application. You don’t want to devote extra time to optimize prematurely on an unproven idea. Yet these decisions can easily bite you down the line.
Consider security. It can be much simpler to just sent HTML in a server response and place that in your app using
innerHTML = response. Cross-site scripting and even SQL injections are common vulnerabilities even today in our pursuit to get something done quickly. These decisions, made early on, can create vulnerabilities years later because focus is drawn in directions of more pressing matters.
Consider privacy. It’s very easy to just stick user data in a database without really considering access rules. It’s easy to believe you’ll add access control and encryption later. Your authentication may just store passwords in plaintext, but that’s temporary. But once your app suddenly is growing by the thousands, it would be bad UX to reset passwords and force them to create new ones the right way. Yet this is something that will happen sooner or later, potentially if your hand is forced.
In one project I’ve been developing, I’ve found premature optimization to help ensure it scales over time. It’s a game in which I’ve regularly added new characters and items. As the game has evolved, it has grown more complicated and harder to manage. It’s led to a lot of effort in refactoring. Pretty much none of the original code at this point is used, which suggests a lot of engineering time and effort at the start was not good enough.
In my refactoring, I’ve created functions and data structures that are arguably too complex. They accept many parameters, and may return complex data responses. I think about complex scenarios, what the code may look like a year from now. In my first round, I dismissed such ideas, rushing in with something that “just works”. Now, I’m much more interested in building code that I won’t need to replace.
And this works. My refactored logic is future-proofed in that I can extend it with new function parameters, new return values, and new capabilities as my game mechanics evolve without impacting existing modules. While my game may not currently have a need today, I might actually need it in the future. Moreover, a more functional design has made unit tests much easier to write and provides greater reliability in the face of constant change.
This has certainly saved me countless hours and allowed me to focus on important matters.
Maybe five years from now it’ll become stale again, but that’s much more time than I would’ve had otherwise.
I realize that this is much easier said than done. A lot of developers advocate for patterns like MVC, model-view-controller, which should make development more scalable. Of course it is still easier to just embed CSS into my HTML pages, with a short
<script> at the bottom. It just creates potential problems for future me.
What I’m wondering is why we don’t just do the right thing the first time. But I get that it’s much easier said than done. This shouldn’t be the burden of every programmer to build on their own. While we need to ensure that we have a solid and scalable foundation, it should also be the work of independent open source projects to host and manage that scaffolding.
Building a mobile app? While templates exist for the MVC pattern, we would be improved greatly by more sophisticated and opinionated templates. Something like a FTM pattern: Feature flags, Testability, and Modular design. Splitting this scaffolding and letting developers focus more on their skill sets could go a long way in producing more reliable software in a scalable way, with less refactoring and greater productivity.
In the meanwhile, I think I’d like to continue to write software in a way that keeps one eye in the future. Maybe it’ll be unnecessary, but I might actually need it.