Hey friends đ Welcome to a new edition of Frontend at Scaleâyour biweekly newsletter for all things software design, architecture, and truly terrible dad jokes. Today weâre taking a look at one of my favorite code smells. Or should I say, weâre taking a sniff at it? Ok, I can see you shaking your head, but I did warn you about the jokes, didnât I? Donât worry, I promise the rest of the newsletter gets better. Sort of. Letâs dive in!
SOFTWARE DESIGN
Too General Too Soon
We should talk about that smell in the air. No, not that one (what is that smell, by the way? Did I forget to take out the trash again?)
Iâm talking about the code smell that comes up whenever we grab our crystal ball and try to predict the futureâSpeculative Generality.
âSpeculative generality is what happens when we (intentionally or not) write code today to solve problems we might run into tomorrow. This could mean adding extra features prematurely or making general-purpose (and reusable) abstractions that are used in just one place.
I think itâs important to talk about this code smell in particular because it happens very frequently and, if left unattended, can cause some serious damage to your codebaseâs maintainability. So today I want to cover two things:
- Why speculative generality is so dangerous, and
- Why, sometimes, it can actually be a good thing.
I know it looks like Iâm contradicting myself here, but please, allow me to elaborate.
The Dangers of Premature Generalization
Imagine weâre building a new dropdown component for an app weâre working on. Itâs a relatively simple component (famous last words), but it has some fancy styles and animations, so it needs to be custom-built.
In addition to the dropdownâs core functionality, there are a bunch of other features we could add to it to make it more reusable and general-purpose. Some are small, like adding a new prop to disable the dropdown (even though we donât have a disabled use case yet), and some are big, like adding multi-select capabilities.
When we sit down to implement this dropdown, we essentially have two options ahead of us: we can just solve the problem that we have right now (the dropdownâs core functionality), or we can solve some of the other problems as well, with the expectation that doing so now will save us time in the long run.
â
A sensible answer to this dilemma is to go with the first optionâonly solve the problem that we have right now. Why? Because the alternative is too risky. Weâre essentially making a guess about which problems weâll run into a few months from now. And if weâre honest, weâre not very good at predicting the future.
You might know this principle as YAGNI ("You Aren't Gonna Need It"), which warns us against premature generalizations because:
- They add a bunch of unnecessary complexity that makes the code harder to understand, and
- They often mean that weâre spending more time today building features that we donât know if weâll need tomorrow.
But thatâs not the entire picture. In fact, YAGNI is the least of our problems here. The real danger of speculative generality is much sneakierâit hides deep under the surface and only becomes apparent once itâs too late.
To demonstrate this, letâs fast forward a few months when a new actual requirement comes in. Itâs not one that weâve prepared for, but something new. For instance, our dropdown might now need the ability to create option groups.
If we took the route of only solving the problem at hand back when we initially implemented the dropdown, adding this new feature might be straightforward. But if our starting point is the more complicated premature generalization, then trying to force it to solve the new problem might result in a particularly messy abstraction.
This is how code naturally evolves. As developers, we have a tendency to follow existing patterns in the codebase, so when new requirements come in, weâre much more likely to make the new problem fit the existing solution than to consider if there is a better way to fix the problem.
We could clean up unnecessary features, of course. But that would take time (usually in the form of a large refactor), and it might not always be obvious which parts of the code arenât being used.
This happens more often in parts of the application that change frequently, which only exacerbates the problem. No matter which type of software youâre building right now, I bet the messier parts of your codebase are the ones that change the mostâand also some of the most important in the entire application.
With all these downsides, it seems that we should avoid speculative generality like the plague, right? Well, not always. We should be careful around it, for sure, but there are cases when a little speculation can be a very good thing.
Speculative Generality vs. Optimizing for Change
Youâll notice that this âdonât try to guess the futureâ advice is somewhat at odds with another thing we talk about frequently in the newsletter, which is the importance of optimizing our codebase for change.
As with most things in software development, there is a tradeoff that we should evaluate before making a decision.
Avoiding premature generalizations is a good rule of thumb, but we shouldnât follow this advice blindly. Sometimes, doing a bit of extra work ahead of time can truly save us from costly refactors in the long run.
Letâs use internationalization as an example. Imagine our app only supports one language at the momentâEnglish, but our hot new startup is expanding to South America, so we want it to be fully translated into Spanish as well.
If we strictly follow the ânever generalizeâ advice, weâd just do the minimum amount of work to support one extra language and no more. But it doesnât seem that much extra effort to support an unlimited number of languages, so⊠should we do that instead? Here are a few questions you can ask yourself to figure that out:
- Is there a non-zero chance that weâll need to add support for at least one more language in the future?
- What is the effort of adding support for N languages vs. adding support for just one more language?
- How much complexity does the more general solution add to the codebase? Can it be encapsulated so that developers using this feature are not exposed to this extra complexity?
Some of these questions might not have a concrete answer, and we might be required to make a series of âbest guesses.â But considering these questions carefully will help us make a more informed decision about whether itâll be more beneficial to generalize a solution early or not.
Evaluating this tradeoff can be challenging. Here are a few other tips that can help you navigate this delicate balance:
- Use the Rule of Three: in his book The Rules of Programming, Chris Zimmerman tells us that âgeneralization takes three examples.â Before creating a reusable abstraction that you might not need in the future, wait until you have at least three concrete use cases.
- Embrace composition: we often talk about composition from a UI-component standpoint, but you can use this principle any time you write a function. Separate your logic into small modules that follow the single responsibility principle. Composable modules can either be used independently of each other, or combined together to solve new and novel problems when they come up.
- Follow the Last Responsible Moment principle: when in doubt, ask yourself if you really need to make a decision right now about whether to generalize or not. If you can postpone this decision without any repercussions, you should do so and wait until you have more information (e.g., when you have more certainty about whether a feature will be needed or not.)
Like any other code smell, speculative generality doesnât always indicate an actual problem. Itâs important that we notice when it happens so that we can make sure we understand the tradeoffs involved, but we shouldnât always try to âfix it.â
Creating a solution that is too general too soon can certainly be dangerous. But generalization can be used for good as well, even when done prematurely.
Youâll most certainly face this dilemma multiple times throughout your career as a frontend engineer. Making a decision is not always easy, but if you learn to recognize this smell and make a habit of evaluating its tradeoffs, you'll have everything you need to make the right call.
ARCHITECTURE SNACKS
Links Worth Checking Out
đ READ
- All this talk about generalized abstractions reminded me of tefâs classic essay on writing code that is easy to delete, not easy to extend. Itâs definitely worth reading (and re-reading) every once in a while.
- Have you heard of the AHA Stack? Itâs a brand new (yet somehow familiar) way of building interactive applications using Astro, HTMX, and Alpine.js.
- And since we can never have enough Astro content, hereâs a guide to using Astro with Qwik that Paul Scanlon put together.
- Todd Gillies wrote a nicely illustrated guide to understanding good versus bad code that even your non-technical teammates will enjoy.
â
đ„ WATCH
âGet a Whiff of This by Sandi Metz
Speaking of code smells, Sandi Metz has a great talk about them that you should absolutely watch. You know how much I love Sandi's talks, and this one is not the exception. Youâll learn not only about the different categories of code smells, but youâll also see a few examples of how to spot and fix them. All in just a few minutes!
â
đ§ LISTEN
âFrontend Masters Podcast Ep.9 with ThePrimeagenâ
Iâm a big fan of Frontend Mastersâ new podcast, and their latest episode with ThePrimeagen is a particularly fun one. It's a super engaging conversation that covers everything from content creation and technology trends to work-life balance and beyond. And I really mean engagingâI was late to an appointment because I lost track of time listening to it đ Highly recommended! Unless you have an appointment in the next hour, in which case, maybe set up an alarm first.
Thatâs all for today, friends! Thank you for making it all the way to the end. If you enjoyed the newsletter, it would mean the world to me if youâd share it with your friends and coworkers. (And if you didn't enjoy it, why not share it with an enemy?)
Did someone forward this to you? First of all, tell them how awesome they are, and then consider subscribing to the newsletter to get the next issue right in your inbox.
I read and reply to all of your comments. Feel free to reach out on Twitter or reply to this email directly with any feedback or questions.
Have a great week đ
â Maxi