Every senior developer knows the feeling. You inherit a code base that makes you wonder what the previous developer was thinking. Comments are sparse, methods are too long, architecture seems to defy every principle you hold dear. But one thing about having 25 years of experience has taught me: approaching legacy code with frustration is like Marcus Aurelius getting pissed off at the Roman Empire he inherited – messy, inefficient, but ultimately something that requires discipline and strategic thinking to improve, not condemn. Both require the same discipline, patience, and strategic thinking to transform rather than to condemn.
The Legacy Code Dilemma
Picture this: you're tasked with maintaining a critical service that handles user payments. You dive into the code and find nested HTTP calls to external APIs, each wrapped in a try/catch block. Sounds reasonable, right? Except every single catch block is completely empty. No error handling. No logging. Nothing. When things fail (and they do), the system just... silently continues, leaving users confused and support teams clueless.
That's the moment where most of us want to throw our hands up and scream, "What the hell were they thinking?"
But here's the thing, we just have to accept the circumstance for what it is. We can't change it. The code exists how it is. It's up to us to try to make sense of it, try to understand it.
And it's not necessarily even just legacy code, either. You could start a new job at a new organization with a code base that's still actively worked on, but it's such a large code base that it's really hard to wrap your head around all the different aspects: what decisions were made, why they were made. There's just a lot of context you don't know.
Understanding Context Before Judgment
Here's what I've learned after dealing with countless "WTF" moments in code: we don't know the context. We don't know what the constraints were. We don't know the budget. We don't know the timing. There are so many factors that go into why code is what it is.
Maybe those empty catch blocks weren't the result of a careless developer. Maybe it was a junior dev who was told to "just make it work" under an impossible deadline. Maybe the logging infrastructure wasn't in place yet. Maybe they were dealing with a third-party API that was so unreliable that proper error handling would have crashed the entire system.
I've been on that side as well – producing code that I'm not exactly proud of, but it did work. And you might look at it and go, "Why?" But it goes back to those constraints, the timing, so many different factors that you just have to accept. Those things are outside your control, so just focus on what you can produce now.
This is pure stoicism in action. Marcus Aurelius wrote about accepting what's beyond our influence while focusing our energy on what we can actually change. Energy spent criticizing the past is energy not spent improving the future.
The Incremental Improvement Approach
Marcus Aurelius always emphasized daily progress over dramatic transformation. The same applies to legacy systems. You're not going to solve it all in a day. It's all about that slow churn of just taking one thing, one feature, one function and making it better; more readable, more testable, more maintainable.
With those empty catch blocks I mentioned? I didn't rewrite the entire service overnight. Instead, I started small:
First commit: Added basic logging to understand when and why failures were happening. Second commit: Introduced proper error messages for different failure scenarios. Third commit: Added retry logic for transient failures. Fourth commit: Created fallback mechanisms for when external APIs were down.
Each change was small, measurable, and immediately improved the system's reliability. Just like stoic practices, consistency beats intensity every time.
We refactor incrementally rather than attempting a massive rewrite, both in code and our personal habits. When you're refactoring code, you start with the smallest part and work your way up. You introduce tests, introduce systems to ensure you're not breaking anything.
It's the same with our lives. We should be making small changes that we can actually measure instead of trying to do everything at once. Because if things don't work, or even if they do work, we don't really know exactly what it was, so we can't learn how to improve in the future.
The Boy Scout Rule: Leave It Better Than You Found It
Each commit, each daily practice should leave things slightly better than how you found them. It's all about improving one day at a time, one step at a time, one commit at a time.
It's like applying the Boy Scout rule: leave the campground cleaner than you found it. This is essentially stoic ethics applied to programming. We just try to make the code better than how we found it. Cleaner, more testable, more fluent, easier to understand, especially for future developers or teammates.
You're working on a new feature; you want to make sure that somebody who has to come into that code in the future knows what they're doing, as opposed to trying to pull their hair out like you probably did.
When to Refactor vs. Rebuild
Stoics distinguished between what could be improved and what must be completely replaced. Some legacy code, like some inherited beliefs, is too fundamentally flawed to salvage.
Wisdom lies in recognizing the difference and having the courage to start fresh when necessary. Both require understanding the core requirements and values that must be preserved while discarding the implementation that no longer serves.
So like in life, sometimes we have to start from scratch. Sometimes we have to throw it all away and start over. Other times it makes sense to take what we already have and just make some tweaks, to try and improve the process little by little.
It really comes back to what we're trying to accomplish. From a coding perspective, it also comes down to timing and budget. If you have the resources for a rebuild, it might make sense. But if you don't, or if there are so many interdependencies or critical parts requiring specialized knowledge, it might make sense to go with a slow refactor.
It's the same in life: sometimes you get into situations where you can't quite rebuild; you kind of have to just do little things and try to improve upon them. But sometimes you find yourself at that fork in the road with an opportunity for real change and rebuilding yourself.
Embrace the Process
Whether you're dealing with nightmare legacy code or trying to change ingrained personal habits, the stoic approach remains the same: accept what you cannot change, focus on what you can control, and make consistent progress over time.
That API service with the empty catch blocks? Six months of incremental improvements later, it became one of our most reliable systems. Not because I performed some miracle rewrite, but because I applied stoic principles: patience over frustration, progress over perfection, and daily discipline over dramatic gestures.
The person who can push you furthest toward your goals is the same person who can hold you back: you. And the code that seems most hopeless today might just become tomorrow's success story, if you approach it with the right mindset.
Either way, the only thing we can do is choose wisely. And whatever decision we make, we just have to go all in on it. That's the stoic way. That's the developer way. And honestly? That's the only way that actually works.
Quote of the Day:
"You have power over your mind - not outside events. Realize this, and you will find strength." - Marcus Aurelius
👉 If you enjoy reading this post, feel free to share it with friends!
Or feel free to click the ❤️ button on this post so more people can discover it on Substack 🙏
You can find me on X and Instagram.