Table of Contents
In a quaint bar on the outskirts of Catania (Italy), as whiskey glasses clinked and muted conversations blended into a harmonic background hum, an old-timer once told me, “The best drink isn’t the newest bottle on the shelf; it’s the one that’s aged just right.” Now, while he was probably quite drunk and didn’t speak a word in English, as well as this being a fictional story, I couldn’t help but draw a parallel to our world of incessant coding and technological innovations. Our constant need to rewrite.
There’s this great concept in budgeting — Aging Money, which is a solution that helps you build a solid financial foundation and to absolve yourself from living paycheck-to-paycheck:
A dollar is born the day it arrives in your life. Let’s say you’re on your way to work Friday morning. You can’t afford to put gas in the car, but you get paid later today, so you’ll do it on the way home. You get paid, you cash the check, and then fill the tank. When you buy that gas, you’re spending money that’s barely 15 minutes old. It barely arrived in your world, and it’s headed right back out the door. This immediately creates uncertainty. You want to get to the point where money hangs around for a while before heading back out the door.
Let’s break it down a bit. To simplify — the idea is that you spend the oldest money in your account first. This system, counter-intuitive as it may sound in our instant-gratification culture, is a cornerstone of sound financial stability. It gives you a buffer for emergencies, it smooths out your cash flow, and it provides you stability in the form of a nest egg for investments. In this case Old Money > New Money.
I’d like to take this concept one step further — old is better.
Of course, the allure of the new is intoxicating. It beckons with the promise of exciting possibilities and the thrill of being on the cutting edge. Who doesn’t want to implement the newest framework, consume the latest white noise on Twitter (Or X, or Whatever), or try out that new GPT LLM? But, as with any intoxication, there’s a hangover waiting on the other side. In our chase for the brand new, we often overlook the value of what’s stood the test of time.
It’s similar with news. God knows every day there’s a fresh hell or wonder being reported. But here’s a secret: the really important stuff? That has a longer shelf-life. There’s so much happening every day, that the things that matter to you — those things that you will remember in 5 years — will still be relevant tomorrow, or next week. You have enough time to read it, digest it, and ponder on it. It won’t spoil like milk; in fact, it’ll age like wine, providing new nuances and complexities as time progresses and more context comes to light.
The same applies to code. New libraries. New languages. New Frameworks. New Intern coming in and thinking he can rewrite better parts of the code himself. It’s easy to get swept away. But is the newest framework always the best choice? Is a rewrite really going to make everything better? Or is there wisdom in the code that has been around for years, has been tested with crazy edge cases, and has evolved together with the business?
As we dive into the world of IT, where systems seem to age faster than the Sicilian wine in my room, we’ll explore the beauty and benefit of mature codebases, and why sometimes it’s best to let codebases stay as they are and just.. slow down.
The Wisdom of Old Code
Why should you consider aging your code? Because the longer your code has been around, survived different cataclysms (read: business pivots), and evolved, the more robust it is. The team that has built it before you had time to debug, to optimize, to improve — the code has accumulated years worth of bugfixes that are in places you cant even imagine.
The kinks have been worked out, and what you’re left with is a mature, stable system that can handle whatever comes its way. (Or maybe a big pile of technical debt, which we’ll talk about later).
🏄 You see, in this fast-paced world of constant refactoring, there's something to be said for stability. Aging your code isn't about resisting progress; it's about ensuring that when progress happens, it's built on a rock-solid foundation.
Drifting away from Original Architecture
You remember that eager intern who joined your team and wanted to change the world on day one? So full of ideas and yet so naive about the complexity of the existing systems. He’s the embodiment of the new framework that caught your eye, promising a whole new world. So you rewrite pieces of code, integrate it, and just like the intern who quickly finds out that corporate business rules are not as simple as they seem, the new code soon learns that fitting in isn’t that easy.
Every time you add a new library, it’s like adding an extra room to a house. But what happens when the room you add doesn’t quite match the existing architectural plan? What if the new room demands more power than your electrical system can handle or affects the foundational structure of the house itself?
Joel Spolsky writes in his essay “Things You Should Never Do“:
There’s a subtle reason that programmers always want to throw away the code and start over. The reason is that they think the old code is a mess. And here is the interesting observation: they are probably wrong. The reason that they think the old code is a mess is because of a cardinal, fundamental law of programming:
It’s harder to read code than to write it.
This is why code reuse is so hard. This is why everybody on your team has a different function they like to use for splitting strings into arrays of strings. They write their own function because it’s easier and more fun than figuring out how the old function works.
That’s what happens to your IT system when you toss in a new library or start rewriting without a second thought. You’re not just pulling another block from your Jenga tower; you’re deviating from the original design as well as years of historical knowledge that grew around it.
Losing Simplicity, Gaining Complexity
As you stray further from the original design with mixing different frameworks and random acts of integration, something insidious happens: you begin to lose the conceptual integrity of your whole system. Remember that moment when you had to write a 10-page manual just to explain how to run a straightforward function? That’s a symptom. Your codebase has become the equivalent of an over-decorated Christmas tree, so loaded with ornaments that you can’t even see the branches.
Loss of system integrity is not just a fancy phrase; it’s the fast track to Complexity Hell. And Complexity Hell is not a nightclub; it’s a Dantean inferno where you spend days debugging code written in several different ways, weeks reading Github Issues of different frameworks, and months trying to integrate new features in all the right places that should’ve taken hours.
Every new rewrite of some piece of stable code not only adds to complexity but also brings its own maintenance costs. Sure, that library looked great in the demo, but now you have to keep it updated, make sure it’s compatible with the rest of your system, and oh god, did it just break the build? What was supposed to be a quick fix becomes a long-term liability.
And let’s not forget about adaptability. Remember how easy it was to add new features when your codebase was just tens of thousands of lines? Those were the days, right? The more you stray from your original architecture, towards a patchwork of different “shiny new things” (that overtime became dull), the harder it becomes to adapt. You find yourself navigating a maze of dependencies, conditional statements, and weird bugs that have no business being there.
If adaptability is the currency of the modern tech world, then a complex, bloated codebase is like having your assets frozen. You can’t move; you’re stuck. And while you’re standing there, frozen in the headlights of complexity, the world moves on and opportunities pass you by.
Asking the hard questions
So before you get seduced by that desire to rewrite everything with a new framework that promises you “Increased X” where X is usually one of “Innovation, Performance or Flexibility”, take a step back. Have a hard look at your reliable codebase. It may not have the glitter of newness, but it has the glow of maturity. Remember, you’re not just writing code; you’re building a legacy for future developers. And legacies aren’t built on fads; they’re built on foundations. Foundations that can withstand the test of time, the whims of the market, and yes, even the allure of the new.
Take a really hard look at the situation:
- What are the goals that you’re trying to achieve? Are they substantial enough?
- Do these new additions conform to your overall architecture?
- Do they fit your development paradigms?
- Will you have to go out of your way to integrate it? Will others?
- Are you sure this module needs to be optimized/improved/rewritten or is it just your ego talking?
🏄 Just so we’re on the same page — aging code isn't about resisting change or sticking to your guns while the world moves on. It's about recognizing that new isn't always better and old isn't always obsolete. It's about understanding that foundational strength isn't the antithesis of innovation, but its prerequisite.
In this regard, it’s also crucial to acknowledge that there are scenarios where opting for new technology or a major rewrite is not only warranted but essential.
Here are a few scenarios to consider:
Technological Advancements: One of the driving forces in the tech industry is innovation. New frameworks, languages, and tools are developed to leverage new hardware capabilities. Ignoring these innovations completely can lead to missed opportunities. It’s essential to evaluate whether a new technology aligns with your goals and offers significant advantages over the existing stack.
Technical Debt Overload: While some technical debt is manageable and can be strategically addressed, there comes a point where an accumulation of technical debt becomes overwhelming. If your codebase is riddled with complex workarounds and patch upon patch, even if it works — it might be time for a refactor to regain maintainability for future business needs.
Changing Business Requirements: The business landscape is dynamic, and sometimes, old code may no longer align with evolving market needs. If your current technology stack restricts your ability to respond quickly to customer demands, it may be worth considering rewriting in some framework that allows you to do that.
But coming back to “your mileage may vary”.
Each situation is unique, and the decision to refactor, rewrite, or adopt new technology should be based on a careful assessment of your specific circumstances, business objectives, budgets, team expertise, and technical considerations. The key is to find the sweet spot where mature systems and innovative technology can coexist harmoniously.
Other Newsletter Issues:
- Rules of Thumb for Software Development Estimations
- The Surprising Power of Documentation
- Being a good mentor – a developers guide
- Things they didn’t teach you about Software Engineering
Reactions
New!
If you’re finding my articles valuable, consider sharing it with friends, or buying me a coffee.
Also, ask me anything in a personalized question, if you want my expertise.