As a company develops and scales up its business operations, its code’s sophistication must follow. That is, the supporting technology must evolve in step with the growth of the organization to ensure that the sprawling company does not collapse under its own weight. But this is only in the ideal view of the developers. New features are often added to the written code without allocating time to refactoring it, which is altering and converting the code into a more understandable “language.” It is often unclear to business people why there is a need to allocate resources to updating underlying code to a program functions. The reason is that if something malfunctions or if there will be a need to build upon or revamp the existing solution, it will be useful for specialists to be more familiar with the code at hand.
Not everyone knows that legacy code begins to appear almost from the first months of a project’s life if it has a sufficiently active codebase. Few people think in advance of the full-fledged architecture of the code and the system in general. It is almost impossible to do unless you are developing a typical project.
Complex solutions are often developed with a fully formed idea, or several months after the release, it turns out that something needs to be added. The code is also modified to update the solution, but unfortunately not wholly, but only partially. It is how the legacy code appears. Let’s give a definition to this concept and see why it is so scary.
What is legacy code?
Legacy – translated from English means “inheritance.” As practice shows, heredity can be very difficult. Because source code inherited from someone else or inherited from an older version of the software can be so confusing that it’s not clear how it works. However, in general, legacy code is characterized by the following aspects:
- The original developer is no longer maintaining the code
- The code was initially written for operating systems that are outdated and no longer supported
- You cannot use automated tests to find bugs in this code
Michael Feathers introduced a definition of legacy code as code without tests, reflecting the perspective of legacy code being difficult to work with in part due to a lack of automated regression tests. He also defined characterization tests to start putting legacy code under test.
And if you are dealing with legacy code, we should talk about the legacy system, apart from issues related to the actual old code, developers will have to face:
- outdated technologies;
- partial or even complete lack of documentation;
- deprecated methods in libraries that are used in your project;
- unsupported packages or apps.
Suppose you are the owner of an outdated system that no longer solves the necessary business tasks. In that case, first of all, you should look for the developers who developed this system or those who have experience working with similar projects. It is because beginners in the area of legacy code infrastructure revamping are likely to spin their wheels trying to figure out the weeds of the processor are primed or destined to abandon the project without completing it.
According to our experts, legacy code is not so destructive. There is a certain logic that if the software worked well for north of 5 – 10 years, and a noticeable issue with the software is discovered after, let’s say, a full 18 years, it is not unreasonable for the beneficiary to wish for the solution to be resuscitated or modified into a better version. It should therefore be amended cautiously and diligently.
We realize that if we are tasked with working with a software product whose code is outdated but continues to work in whichever limited capacity and otherwise helps make money. The cost of error is then very high. Henceforth, the study and analysis of the program code and the construction logic is the number one task on our priority list.
What other challenges do developers face when working with legacy code?
- development of new functionality if the software is still working, which means developing and expanding upon existing legacy code, which could necessitate and imply refactoring as well
- bug fixes
- optimization and stabilization of the system. (This point is often overlooked, but it is an integral part of the process)
So how do you know when it’s time to refactor or rewrite the code of your software? In the next paragraph, we’ll look at real signs that it’s time to look for development documentation as well as the developers themselves so that your digital solution can continue to work successfully.
Why is a legacy code a challenge?
There are two main reasons why legacy code is a thing. The first reason is that new versions of operating systems, languages, libraries, etc., are constantly being released. The problem is especially relevant for mobile applications and scripting languages - in each new version of the platform, it is necessary to fix compatibility problems with the old code. The truth is that as much as this is inconvenient and uncomfortable, it is unavoidable.
The second reason is that the beneficiary of the software often wants to shorten the development time by shortening or altogether abandoning design, automated testing code review, approving third-party libraries that may not be supported. If the development team fails to effectively document the logic and thinking that went into the process on the way to the outcome, it is likely to set you up for difficulties associated with untangling and deciphering the code in the future you will have to work with it again.
We see cost reduction among startups even at the MVP development stage, and all because most of the products have a rather limited life cycle. In other words, the period of high demand for a product lasts on average three to four months. During which, if competitors see potential in your solution, they are likely to copy your work and perhaps even do better with their version and spin on your concept. Therefore, 97% of startups destroy their best practices and continue to experiment before finding the same product that will remain in continuous demand on the market, jostling for market share. Considering the product life cycle, large companies like Apple or Oracle release new products every quarter, thereby preventing the newest product sales from falling off a cliff.
Furthermore, let’s take a look at some examples of nuances and nuisances that you might encounter when unpacking the legacy code:
- When you cannot add new [features, sections, etc.] without also rewriting the old code
- introducing new people to the project is challenging and lengthy and can take more than two months
- When it is not possible to configure “Continuous Integration or Deployment.”
- When simple bug fixes take a lot of time
- When the platform the app is running on is no longer supported.
- When an increase in the number of users is expected, the old system will not be able to withstand
Legacy code isn’t always problematic. For example, the WordPress package has horrible code, but 38% of all internet sites are based on it. There are no problems with typical work, it is worth adding a non-standard code to WP, and that’s it – automatic updating will be impossible. Also, you may not have problems with software if it does not interact with the world. For example, it has isolated services in service architecture.
Suppose you recognize your problem in the above paragraphs. In that case, we suggest that you conduct a code audit that will help you understand how to improve your software’s operation and whether it is worth considering rewriting your solution from scratch.
The strategy of working effectively with legacy code
And so you have legacy code that you realized you need to improve upon. But don’t be in a hurry, as primary code changes almost always lead to unexpected problems. To do everything correctly and without losing existing code, however bad it works, let’s consider a strategy for working with legacy code. Be warned right away that without reliable testing, application crashes are inevitable.
Before you start upgrading your software product, you need to prepare a test plan and work out the mechanism for releasing new versions with the capability always to be able to roll it back and revert back to the previous one. It is better to check the API and the work of the main logic with automated tests.
We suggest using static analysis utilities to analyze PHP legacy code and compile a list of compatibility issues with the new PHP version. We will consider an example based on this particular programming language since we ourselves use it for development:
- Rector – will solve minor incompatibility problems with the updated language version by automatically adjusting the code to reflect the new version of the language.
- Exakat – analyzes code compatibility by PHP version, generates a list of used extensions, problem areas of the code, and, as a result, points out and makes a list of tasks for revision.
- Phan – can be used to detect lexical constructs in your code that are not used in newer PHP versions.
If there is no extension for the new language version used in the application, the code sections with calls to the missing permissions will have to be rewritten.
Moving from a monolith to a service architecture
This clause is addressed to those whose legacy solution is monolithic. As we said, the software must evolve as the business evolves. You should consider Lehmann’s laws, which state that the software’s complexity grows, the functional content expands, along with them, the staff of developers, and the amount of code is continuously increasing. Nobody plans for updating or replacing obsolete software when laying the groundwork for the development budget, which will inevitably put a dent in a project’s feasibility and financial performance. However, the software’s quality does amortize or deteriorate with time, and the size of the Git repository can be calculated in gigabytes. The development team’s workload grows over time, and it becomes more and more difficult for the company to release new software to keep up with ever-growing needs. Usually, in such a situation, a decision is made to split up the monolith.
One of the most popular options is supporting an old working solution, creating new services, often in a new language – for example, in Golang. The risk in this situation is that it will not be possible to create an exact copy. Service requirements and other factors would have changed since the inception of the software. As a result, you will have to start everything practically from scratch, namely from the discovery phase. But this time, you will approach this digital solution development in a way that would solve business problems and remain relevant for a long time.
When breaking up a monolith, the most practical approach would be to divide the monolith into parts – the models, fix the API, and then split up the work within them into services. First, parts of the application code must be separated into separate packages. Then services can be created from the boxes.
Moving code into packages opens up several possibilities:
- developers from different teams can only provide the public API of packages and restrict calls to internal classes
- you can describe dependencies between your modules and use composer to manage dependencies and versions of your boxes
- each module can have an independent development cycle, and the work on the project can be scaled
- you can release different versions of packages and agree on API changes
Depending on the software size, piecing a chunk of code into a package without rewriting can take from one to several weeks. There are days that developers end up transferring to packages a thousand lines of code a day with inversion of external dependencies, then fix API modules, making large-scale refactoring easier.
Imagine that you managed to split the code into packages, but when you put everything together, everything is assembled into one application and works in one process, like a monolith.
Many people wonder how the notion of “microservices architecture” works in practice and what the full process all the way through to achieving the results really looks like.
Each package has a fixed public API. Based on this API, you can create a service with a RESTful protocol. The new service code is the package code, around which the standard routing is written, logs, and the writing of the other infrastructure code. And in the old code, instead of the package code, an adapter appears for HTTP calls via curl.
Two tasks must be solved to create separate internal service applications:
- Detailed logging of calls of all services. Each client request must be assigned a unique call ID passed to all services when calling internal APIs. And every service call should be logged. You need to be able to trace service calls down the chain.
- Guarantee the only result of the request execution when one of the services fails, when the service request is present. Example: a client’s request for payment from his account to another account. Suppose an internal dedicated service fails, records a transaction’s results, and recalculates the balance on user accounts. In that case, a second request to it should not result in two money transfers from one account to another.
But this article is not intended to cover the details of the transition to a microservice architecture from a monolithic one. You can learn more about our practice of developing a solution with a microserver architecture in the article Omni portal.
Rewrite from scratch or refactor
You are probably confused and not entirely sure what to do, rewrite the solution or refactor. Rewriting (rewrite from scratch) is when you rewrite code from scratch without reusing its source code. Refactoring is when, by successive transformations of the old code, you come to its new form. More differences are shown in the table below
We believe that there is no single correct answer to this question. It is necessary to conduct an audit of the code and study your outdated software and business processes in which it is involved. Our business analysts always insist on these stages as we are interested in making your business as efficient and profitable as possible. After a detailed analysis, it will become possible to make an optimal decision, sometimes rewriting is faster than refactoring. It is essential to take all factors into account and make detailed calculations.
Many people think refactoring is safer, and there are several reasons to think so:
- It is done in smaller steps, and such incremental changes are easier to plan, test, and release.
- If for some reason the rewriting is stopped halfway, the customer will not find himself in a situation where the new version of the product has not yet been completed, and the old one remains the same as it was (neither here nor there, and time and money was spent).
Experience with Legacy code
Our team has over 10 years of experience working with obsolete code. The first thing we want to focus your attention on once again is that if you have software with legacy code, you need an audit to make the right decision. What should you do to refactor, partially rewrite, or rewrite the code from scratch? You can only make decisions after auditing the development documentation and the code itself. This approach helps assess the current state of affairs, determine what is missing, and give the necessary recommendations to eliminate the deficiencies.
It would help if you were prepared for the audit and implementation of recommendations after it will take from several weeks to a couple of months. The entire process can take anywhere from a few weeks to a couple of months can be conventionally divided into several stages:
#1 This Is commonly referred to as the “Discovery phase.” At this stage, the team examines an existing digital solution and documentation for it. Our specialist also identifies the requirements and analyzes the set business goals to propose whether it is worth refactoring the code or if it’s better to develop a new one from scratch instead.
# 2 Develop recommendations to eliminate shortcomings in the code and improve the software. Typically, the Business Analyst (Product Owner) will develop detailed specifications that the development team will use to create the soft. You’ll have access to Confluence. After discussing your platform workflow, a business analyst (BA) writes epics and user stories.
# 3 Start work. At this stage, the team must perform high-quality work within the specified timeframe and maintain technical documentation accompanying it as applicable. This documentation will serve as a guiding star for future developers who will come to work with your solution.
In an ideal situation, teams that have just created a new digital product or developed a new feature should contact the technical writer, requesting that she write up a description of it. Everyone benefits from this: the development team sees an accurate description summarizing their work in words. The customer understands what he is getting in his product solution and what it consists of. But let’s be honest – in practice, in most cases, developers end up doing this, which can cause occasional gaps in the documentation. As for our work, if you ask our clients, most of them will tell you that one of the big differentiators that set us apart and make us the preferred developers of choice is the transparent way we go about business. For example, a way that we provide process transparency really stands out is by providing constantly updating documentation support explaining the underlying development processes that are being done, which clients can refer to at any time.
Now that we have discussed our approach to software with legacy code, let’s look at a couple of cases, one of which entailed completely rewriting a code to a software solution from scratch. The other involved refactoring code to modify an existing solution.
In the first respective case, a client from the tourism industry came to us with an 18-year-old multi-element system. It was very outdated. It was no longer possible to update it since it was impossible to merely find a specialist who would work with the required programming language, much less assemble an entire team to work with the status quo cond and find a working solution for it. The software worked only with the Windows operating system and was completely incompatible with macOS and mobile devices.
Our team figured out what needed to be kept and completely outdated and obsolete and needed to be removed. Based on customers’ wishes, a list of features and services that needed to be added to the software was compiled. We offered to start the development of such a multi-component platform with the MVP. It meant that we would first develop the primary core functionality of the app before delving further into developing added functionality “embellishment features.” Our client got a chance to test out the program in the real market, get real client feedback and reviews on it, and ultimately decided to extend software feature functionality based on market needs, internal research, and discussions.
We estimated the MVP of the “one-stop-shop” solution to be USD 119,528. The price may vary based on the features that need to be developed and the time outlay, which we assumed 9 – 12 months to develop that project.
The second case study is of a client approaching us about six years ago. The task was to refactor a part of the application to improve performance, which we could execute successfully. Recall that in this development process, the code is restructured without changing its original function; we could do this and increase its performance by 37%.
Ultimately, we want to say that sometimes you need to rewrite low-quality code which no longer solves the business’ needs. Because of the above, we add that if you are experiencing any problems with introducing new features, performance, etc., you can always contact us for a free consultation.
- The original developer is no longer keeping the code.
- The code was originally written for operating systems that are outdated and are no longer supported.
- You cannot use automated tests to find bugs in this code
How to avoid legacy code in the future?
This is a good topic of discussion. Frankly, any code is outdated today, but if we talk about development from scratch, it is still worth choosing new reliable technologies that will not become obsolete for another 5-10 years (or however long the product’s useful life is. For example, suppose you decide to use Ember in front-end development today. In that case, it is already considered a slightly outdated technology, and in 5 years, it will be like jQuery is today. There are some common practices to follow to avoid issues related to legacy code.
These are all measures you can take to extend the useful life of the code.
Remember, your code will become deprecated someday anyway. It’s just a matter of pushing that “someday” to a later date. We talked about the inevitability of code decay in different contexts and at different points of this article and hopefully hit the point home. It is essential to understand that no matter how readable and well-written your code and how sound its underlying logic is, the day will come when your code becomes legacy. In other words, the question becomes not if, but when. Therefore, keep documentation, keep your software up to date, and choose professionals to work with your code.