February 25, 2022
We all want our software to be operating at its best — in a way that is easy to work with and ripe for innovation. Sometimes, though, we find ourselves stuck with legacy systems that are pushed to prod without proper documentation, without proper testing, while the modules are too tightly coupled, or in a way that is generally difficult to integrate with. When this happens, how do we position ourselves to tackle the problems posed and prevent our own code to become legacy?
“Legacy” often assumes a time frame — something that is old and outdated, maybe something built by someone who is no longer around to answer questions or just built before the best practices are known, however, legacy systems are really any software that is too fragile to be comfortably altered. Something engineers are afraid to touch because the very nature of it carries such a high risk of setting off a domino-effect of things breaking.
It is an unfortunate truth that we are going to have to face legacy systems either internally, for a client, or with another vendor. The question at hand is how we deal with them when we are faced with them.
The first option is to just live with it, but this acts as a damper to your capacity to innovate and grow. It is like freezing yourself in time as the world moves around you: the problems you have remain, and it becomes difficult to integrate with new tools and outside solutions.
The next option is to do a Big-Bang rewrite, a total upheaval of the whole thing. This can be tempting when everything is such a mess you just want to start from scratch, however there’s a lot of risk involved in this and a high rate of failure.
We believe the solution is really an iterative process that reflects the scout’s rule: leave the code cleaner than you found it (Robert C Martin’s idea from his book “Clean Code). This process focuses on breaking the problem down into smaller individual pieces and then following the pillars of loose coupling, good testing, and good documentation.
The ultimate goal is to have independent parts that work well together in a way that we can understand. That begins by separating everything out and adhering to the single responsibility principle.
When separating pieces out, it is important to first understand where your main painpoints are. Identify the parts of your system/code that you are the most afraid of touching. Combine this with your current roadmap of features and find what areas make the most sense to tackle first.
The single responsibility principle asserts that every software should have only one reason to change. They can have multiple functions to carry out, but should have only one reason to be modified or updated. By separating each module out, you can clean house of the components no longer serving the software well and will give each module enough space to interrupt the aforementioned domino effect.
When you are dealing with different components, sometimes you need to have different solutions to meet their individual painpoints. Microservices are one solution that also decouples your software components. However, microservices may not always be a fit for your system, so this can just as easily be achieved with properly namespacing your software.
Once the components have been decoupled a bit, it is important to add tests that cover all the remaining modules. Writing tests is a skill that gets developed with experience and can sometimes get left behind when deadlines are fast approaching, but are essential to monitoring and maintaining your code.
The most important aspect of fixing legacy systems lies within how one is documenting the changes being made. It needs to address both the decision making process and how one would use the interface. Good documentation should:
Documentation should be written plainly enough for a human or a machine to understand. The goal of this is to be thinking proactively enough to help whoever needs to work with the interface in the future understand the context around the decisions made to get the software working as it appears, and to allow integration to happen smoothly.
An example of clear documenting is this Decision Record Template by Michael Nygard as shared by Joel Parker Henderson:
In each ADR file, write these sections:
What is the status, such as proposed, accepted, rejected, deprecated, superseded, etc.?
What is the issue that we’re seeing that is motivating this decision or change?
What is the change that we’re proposing and/or doing?
What becomes easier or more difficult to do because of this change?
The industry is not always great at communicating how to use the software we build, but as more of a community is built there is a growing conversation on best practices. Standards like Swagger or Java Docs seek to standardize documentation and provide a model that is more approachable.
The task of modernizing legacy systems is first felt by the engineers who have to do the methodical work of breaking and fixing a system strategically, but the effects impact the business on a larger level and in many ways the whole industry. By following these shared practices to make systems more readable and adaptable, we are able to save time, money, and the energy of our engineers. A clean software system that is well documented welcomes innovation by being easy to work with and understand. The business reaps the reward of a competitive advantage first, but eventually, as that is normalized, it becomes part of the ways we all push each other forward.
If you are dealing with legacy systems in need of an update, working with a team that has the experience of solving these problems before can be a weight off your shoulders. With so many factors to consider, our expert team can figure out what needs to be done to help you reach your goals. If you’re interested in what we do at Compoze Labs, get in touch with our professional team to find out how we can help you!
Ready to build a reliable, connected, and scalable tech ecosystem?
We’d love to Compoze it for you. Contact us today and tell us about your project.