Legacy projects are where development teams go to die. Ok, I’m being a bit dramatic, but keeping teams motivated to work on legacy codebases is challenging for a host of reasons:
- Complexity: Old code can be big and confusing.
- Obsolete Tech: Old systems often use outdated technologies, making work challenging and learning new things difficult. This not only makes work challenging but also keeps developers from learning new, marketable skills, which can make them less competitive in the job market.
- Known Antipatterns: Teams often have to work with known coding practices that are best to avoid. These bad practices can make maintaining the codebase frustrating.
- Technical debt: Shortcuts in code over time can create a tangle that’s hard to change or fix.
- Limited Creativity: Working on old systems usually means fixing problems, not creating new things.
- Slower Work: Old systems might not work well with modern tools like CI/CD, slowing development down.
- No Guides: Many old systems don’t have instructions or notes, making the code hard to figure out.
- Risky Changes: Modifying legacy systems comes with the risk of breaking something that works. It’s not uncommon for a small change to have unexpected and widespread effects due to the complex dependencies in the system.
A few years back, my team was working on a large legacy project that was robustly structured and amply tested but left us doing a lot of what one of my colleagues referred to as “code archeology.” Understanding the motivation behind certain design decisions involved digging through layers of commit messages, cross-referencing commits with old user stories, and attempting to understand the Non-Functional Requirements (NFRs) tied up in each decision.
There isn’t much that can be done to address these concerns for existing legacy projects. Still, there are some things we can do today to invest in preserving the sanity of future generations of legacy code maintainers.
A team can reduce the complexity of a software project by using DDD (Domain Driven Design) and selecting a suitable code architecture, such as hexagonal or clean architecture. Likewise, the same team can mitigate technical debt by judiciously using their development time to identify and address technical debt as part of their normal development activities.
The last point on the list of grievances, however, is something that has a fairly straightforward solution in the form of Lightweight Architectural Decision Records (LADRs).
First proposed by Michael Nygard in 2011, LADRs are a low-overhead approach to capturing the “why” behind our decisions in code. They provide a historical context to our choices, document our learnings, and make the life of future developers significantly easier.
Let’s take a walk down the memory lane of a typical legacy project. You come across a piece of code or a design that seems odd or complex. You have a hunch there’s a good reason for it to be this way, but the original authors of the code are long gone. You’re left scratching your head, wishing that they had left some trail of breadcrumbs to help you understand their rationale.
This is where LADRs come to the rescue.
When a team makes a significant decision about their architecture, refactors a critical piece of code, or discovers something surprising about their domain, they make a note of it in a LADR. This document is a small markdown file describing the context of the decision, the alternatives considered, and the reasoning behind the choice made. The document lives with the codebase, evolves with it, and provides just enough context to aid understanding without burdening the team with excessive documentation.
Remember, Agile software development places value in “working software over comprehensive documentation,” but this doesn’t mean documentation isn’t essential. The primary artifact of architecture is a codebase that is easy to understand and backed by robust tests. However, sometimes the codebase isn’t enough to explain the why, the trade-offs, and the context. In these cases, LADRs are a great tool.
We use LADRs liberally in our projects to provide context around refactors, architectural evolution, and significant domain discoveries. But remember, the use of LADRs must be tasteful. We aim to provide information that can’t be found elsewhere in the codebase.
As you continue delivering working software, remember to watch for those significant decisions, surprises, or refactorings. When you spot one, don’t just commit it; document it. Your future self and the maintainers who will inevitably work on your code will thank you.