Legacy code and what next?
“If a competent programmer, usually a stable, trusted senior, finds that he would prefer to move to another project or go home – know that something is going on. Apparently, debt has been incurred in the past and now there is no one willing to pay it back … “
In this article, you will learn how and why technological debt is created and how to deal with legacy code so as not to go crazy.
How do projects age? The experience acquired in the design of software teaches us that the development and maintenance of business applications often overlook the use of so-called best practices. Based on the assumption that there is no one correct solution to a given problem, those that potentially generate the least costs are chosen. The client’s goals are clear about expectations. The system is to fulfill its business function, it is to work properly in a given environment, it is to be free of errors and sufficiently efficient.
Who would like to think about architecture? Unnecessary overhead. Infrastructure? As always, proven, safe. Maintenance? It will not be as much if we find and eliminate most errors before the production implementation.
Everything is established? Time for choosing the team. Let’s take young, dynamic programmers, they are not afraid of fire, they will even jump into it if necessary. We can start!
The larger the system, the more it takes to build it: requirements, source code, libraries, sets of assumptions, overtime, sleepless nights, unfinished documentation describing the final result, incomplete tests, pre, and post-implementation stress.
In all this rush it is difficult to find the strength to reflect on best practices. At most, good practices are used, already known in the team before, and maybe even tested personally in one or two projects. We want to create a decent, clean code.
Then comes the implementation, a more or less “hot” time for post-implementation patches and you can continue to add functionality.
If technological debt began to arise at earlier stages – and it always arises – there is no time to remove it.
And so the years go by, and the project is getting older… Even the best-started project, which is full of best practices at the beginning, is easy to neglect by cutting investment for proper maintenance. After about five years (sometimes faster) we get a beautiful legacy code that no one wants to touch, and even fewer people dare to modify. Then it remains only to replace it with extremely fashionable these days “cloud solutions”.
How do you recognize the legacy code? When a change, whose premises do not show great complexity, introduced in one place in the application causes a disaster in another place of the system – then we can be sure that we are dealing with legacy code. This situation indicates a breach of the basic principles of software design, known under the carrier acronym SOLID. Most often, such a code will be free of automated testing, opaque; it will make many programmers unwilling. Some people may even get a rash.
Sometimes, there are no disasters, at least not right away. In the case of quite large projects, there is usually a lot of dead code, i.e. areas that are no longer launched in production, but which the most respected legacy programmers categorically forbade to remove. They serve one purpose: wasting customer money, because the changes they make can affect the delicate structure elsewhere, and the time to correct and test them is a waste of time.
If a competent programmer, usually a stable, trusted senior, finds that he would prefer to move to another project or go home – know that something is going on. Apparently, debt was incurred in the past and now there is no one willing to pay it back.
Technological debt When we lack the resources to do a given thing right away, to secure it for the future against the undesirable effects of modification, we incur technological debt.
We think: now we have no money/time/patience to deal with it, so we will first implement the functionality and then we will clean it. Of course, we are not talking about developing ideal solutions. We don’t need them. We just want to have readable, clean code tested automatically, with automated processes of building, static analysis, running tests, and implementation on the test environment after each commit. Maybe not necessarily Continuous Delivery, not yet, but certainly Continuous Integration. We know it’s good. But we will not do it, because the whip called time to market shoots above our heads and mercilessly smudges fingers wandering on the keyboards.
What can go wrong? As with other debts, we will pay more for this one than it would be otherwise possible. Time for corrections will never come. There will always be a more important business goal that will push these unreasonable fancies into the background. They just want to waste money. They did it and it works, right? So what and why should they improve now? And then comes winter. The fields and forests are covered with white fluff, the frost covers the ponds with a thick crust of ice, on which kids merrily ice skate. However, you feel something heavy is in the air. You know that something in this idyll is definitely wrong, because a simple feature from two weeks ago, about which there was a short, pleasant chat planned, is not even half-way done. It was supposed to be delivered on Friday, and today is the second Friday and it is still unknown what is happening. Our senior programmer says it’s hard, there’s a debt or something, nervously raising bushy eyebrows and grinding something incomprehensibly under the mustache. You have to separate the components because they were to have individual responsibilities, and actually even the components themselves are not there. They wrote something three days ago and are testing it today. It doesn’t work and it keeps pouring errors. They need funds for refactoring. So we go to the people from the treasury. Listen, friends, we tell them, we need two working-months here to refactor because it’s bad and it will be even worse. We can’t afford it – they say. There were supposed to be features, not nonsense. You can refactor your life at home. Have a nice day.
Don’t be afraid of change It is estimated that an average programmer reads code ten times more often than he writes it. In a sense, the work of a programmer is art. He must translate the complex business assumptions into beautiful lines in the programming language. He is like a digital poet who composes new stanzas to add domain glory to epics left by venerable predecessors. Only sometimes the rhymes break down, the pace stops, and the ghost enchanted in the machine dissolves in the fumes of absurdity. It is simply not known what the lyrical subject meant.
In this situation, it is impossible to estimate the consequences. Unit tests are needed, but they cannot be written because there are no units. To carry out one simple unit test, you have to evoke half an application. It takes ages and devours a lot of hardware resources. You can’t work like that.
Count the costs – it’s not too late The good news is that it is never too late. You just have to realize this. If the costs of adding functionality in the application are growing too fast from year to year in relation to the business profit from it, the time of delivery increases, error rates, including critical ones, are constantly increasing, their repair times are prolonged, and developers are leaving – these are the premises to look out for.
Even if the situation in the project does not seem so dramatic, it is worth monitoring it on a regular basis. The sooner we know where the budget is leaking, the better for everyone. Once we’ve touched the bottom, it’s easier. Then any way of saving is beneficial, you just have to choose wisely. The necessary changes must be planned.
Change the way you think Let’s respect the programmers. Let’s respect the architects. When they say what the system needs to work – let’s listen to them. When we find out that the first stage of the project will take three months, before we see the simplest framework of the application, do not be nervous. Let us be proud that our system will receive at the very beginning irrefutable foundations on which new buildings can be built and existing ones can be expanded. These people certainly know what they are talking about. They saw systems in times of glory and times of decline. We do not want failure.
What if we don’t create something new, but work on the heritage of our ancestors? Let us not be afraid of changes, but let us introduce them slowly, gradually, in small steps. If there are no unit tests, let’s add support for them first. Let’s configure CI, let them have a place to run. Let’s use the power of version control systems to always have all the branches of code under control, and the main one – always clean. Let’s automate each operation so that it can be retried after the last changes have been rolled out if they are unsuccessful. Most importantly – let’s automize the preparation of test data. Even such a simple thing as a bash script copying files from one directory to another can save a lot of time and torment.
We should also, and perhaps above all, ensure a good mood in the team. If you feel tired, let someone else replace you. When a difficult problem is being considered, it is worth inviting another team for a consultation. Let’s respect the opinion of others. Let’s not press and control too aggressively.
The manager, the director, the man with keys to the treasury – they should all understand that it costs more when things are done haphazardly, even if it seems faster and cheaper at first. In addition, the view that “any specialist can be replaced by a finite number of students” is, in the light of our experience, extremely untrue. It is this kind of thinking about specialists that implies the rise of technical debt. Young people are very valuable, but we build the foundations on experience, not on enthusiasm. Instead of spending half the budget on overgrown, outdated technologies, let’s hire good specialists. They will show us a better way and bring to the teams the best knowledge gathered over the years of hard work. The way of thinking must change at all levels: single person, project team, organization authorities. In fact, everyone must start thinking agile. In fact – everyone must start thinking.