Resisting the Urge to Refactor Legacy Code
Benedikt
January 13, 2025
Definition Of Legacy Code
As software developers, we've all encountered that moment when we look at a piece of our codebase and think, “Who the f*ck decided to use this crappy outdated tool”, only to realize it was you all along. My coworkers and me call this “The Urge To Rewrite”-factor. In the following I want to explore how to interpret this factor and not get to frustrated with it. To be more specific I don't want to talk about complete project wide rewrites, those are talked about a lot and are a whole separate topic. I rather want to talk about smaller updates to the code and technical debt. That being said your view on this topic will probably change as you progress through your career and this is fine, in fact it reflects the experience you gained.
In my definition the essence of legacy code is thinking: "Yeah, I would do that differently now". It's not just old code; it's code that no longer fits the current standards, tools, or architectures we prefer. Whether it's reliance on a library that has since been eclipsed by something more efficient or the use of a pattern that seems outdated now, recognizing legacy code is the first step in the refactoring journey. Another point to consider is that sometimes dependencies are perfectly functional but receive updates with breaking changes. Take, for example, a project I’m currently working on that uses the Fluent UI component library. This library is a comprehensive toolkit, deeply integrated into this project. Unfortunately, the latest version of Fluent UI requires switching to a new CSS-in-JS engine, which means we'd have to modify nearly all our components across multiple sub-projects. While we would love to upgrade, the immense workload involved doesn’t yet justify the benefits—at least until Microsoft decides to deprecate the older version of Fluent UI.
Embracing Blackbox Programming
One thing that helps to concentrate on the current task without getting lost in unnecessary distractions is blackbox programming. If you studied Computer Science that may seem familiar. This approach emphasizes treating certain parts of your code or dependencies as black boxes. In other words, as long as these components are functioning correctly and efficiently, you don’t need to concern yourself with their inner workings. This mindset allows you to focus on the current task without getting sidetracked by the urge to optimize or update parts of the code that aren’t causing any issues. It’s crucial to remind yourself that as long as it works, it’s not your problem—at least for now. Unless there is a compelling reason or a high-priority requirement to adopt the latest tools or technologies, it's often best to leave well enough alone. This doesn't mean ignoring potential improvements forever, but rather prioritizing your workload effectively. By applying blackbox programming principles, you can ensure that your efforts are directed towards solving the most pressing problems and delivering value without getting bogged down by the allure of the newest, shiniest tools. To add to those points:
I, for myself, regularly answer that question with the latter. Ask your team to get a better feeling.
Narcissism Of Small Code Differences
Often, I find myself wanting to tweak small habits of my coworkers when they write code. Most of these issues should ideally be handled by a robust linting and code formatter setup, but occasionally, some quirks slip through the cracks, leaving me feeling a bit frustrated. It could be something as simple as a quirky naming convention or a different programming philosophy that doesn’t align with my own preferences. These differences, although mostly minor, can sometimes make the code feel just not right.
However, it's important to remember that many roads lead to Rome. Coding is as much an art as it is a science, and there are often multiple valid approaches to solving the same problem. It's essential to recognize that these small differences don't necessarily make the code wrong—just different. Embracing this diversity in coding styles can actually be beneficial, fostering creativity and innovation within the team.
Instead of letting these small irritations build up, it can be more productive to focus on the bigger picture and the functionality of the code. Sometimes, you need to move out of your comfort zone and appreciate the different perspectives your coworkers bring to the table. Remember, the ultimate goal is to create a functional, efficient, and maintainable codebase, not to impose a uniform style. By not taking these small differences too seriously, you can foster a more collaborative and harmonious working environment.
A very very good read about this topic is The Narcissism of Small Code Differences by Reg Braithwaite.
How To Communicate With Your Client
Communicating the need for refactoring to clients is another critical aspect. Clients often see refactoring as an expense without a direct, immediate benefit, which makes transparency and education key. In a perfect world I would like to have a maintenance budget from the get-go. Every software project, sooner or later, will need some kind of care. Start by explaining the concept of technical debt and how, much like financial debt, it accumulates interest over time in the form of slower development, higher costs, and increased risk of failure. Also, technical debt does not mean you made the wrong decisions in the beginning, but you acted to your best knowledge. Provide concrete examples of how legacy code will be impacting the project and what improvements they can expect from refactoring and maintenance. It's also beneficial to outline a clear plan and timeline for the refactoring process. This should include a detailed analysis of the upcoming or current issues, the proposed changes, the expected time investment, and the benefits. Demonstrating a well-thought-out approach helps reassure clients that this isn't just busywork but a strategic move to ensure the project's long-term success.
Even though it is "your" codebase your estimation on how long it will take is still only an estimation. Please be careful with estimations and add an appropriate uncertainty buffer. The timing of when to refactor is crucial. Jumping into a refactor during a critical project phase can cause unnecessary disruptions, whereas delaying it might lead to more significant problems down the line. A good rule of thumb is to consider refactoring when the legacy component starts to hinder new development, introduces bugs that are difficult to fix, or when there is a clear, justified benefit in performance, scalability, or maintainability. I would like to highlight maintainability here. This decision should be data-driven; metrics like the frequency of issues, or just how often you talk about something in your team can guide timing.
Moving Forward
Finally, it's essential to remember that refactoring isn't a one-off task but an ongoing part of the software development lifecycle. As technology evolves, new tools and methodologies will emerge, making parts of the codebase outdated again. Regularly scheduled code reviews and updates can help keep technical debt manageable. Keeping clients in the loop with periodic reports on the codebase's health and the benefits of past refactors can foster a better understanding and appreciation for these efforts. In the end, a proactive approach to managing legacy code ensures a robust, flexible, and future-ready software product. Exactly what we all want, as happy developers and happy clients.
If you liked this read you may enjoy my coworkers post about the evolution of data mutations using forms in react as well.
🤖 Statement about usage of AI in this article: This article was written by humans, including the title, concepts, code samples. However we used AI to enhance the style of writing.
You may also like
Irena, 07/14/2024
Why flatMap() is easier than filter() in TypeScript apps
Typescript 5.5
Array Methods
flatMap
filter
map
Antony, 01/30/2025
The Evolution of Data Mutations Using Forms in React
Forms
React
Mutations
Web App Development
Remix
Actions
Optimistic UI
Klara, 12/05/2024
Accessibility – An Essential Ingredient in the Batter or the "Icing" on the Cake?
Web Accessibility
Post Mortem
Konsens
Digital Inclusion
Web Development
Digitale Barrierefreiheit
development
legacy code
refactoring
communication
expectation