Software Development

Legacy-ing of systems – The causes and solutions

1. Introduction

Over the years in consulting, I’ve worked with many applications. Lots of them were new developments that is the fun part of a developer’s job, but most of them were the so-called Legacy applications. What makes an application wear that ugly Legacy tag? Why do we think “Who the hell did this?” and “That is a crappy piece of code!” when we open the source code of a 10 years old system?

In this article, I will talk about my experience with different technologies and what I have learnt over the years in consulting about legacy system. Then, I will show you a simple way to have legacy and messy code in 15 minutes and explain why that happens every time.

2. The causes and the solutions

2.1. The initial technological choice

One inevitable and probably the most common source of application legacy-ing is the technologies that were used at the beginning of the design phase, the initial technological choice that was made at first to answer the question “How are we going to build this system?”.

A real life example of this comes from my experience with one client I had in the last years. You probably don’t know that, but Node.js is not the first Server-Side JavaScript (SSJS) engine on the market. It is currently getting a huge hype because the community has adopted it, because it’s fast and simple to learn. But Node.js, like I said, did not create SSJS. The first SSJS engine was created by Netscape in 1995. The JavaScript code was running on their application server that was called Netscape Enterprise Server which was part of Netscape Suite Spot.

So, it looks like the 90’s architects of my client wanted give SSJS a try. They have developed a little system to query a database and, 20 years later, I have to support that! What I had was a production server with the application running on it, no SVN (or CVS!), no pre-production environments, nothing! I copied the code on my workstation, tried to understand the way those kind of applications are built, how they work, etc.. This is were the fun part starts as Netscape Enterprise Server (NES) was bought by Sun Microsystems and was renamed to Sun Java System Web Server, which was acquired by Oracle in 2010 and renamed to iPlanet. At some point, they decided to drop SSJS support because nobody was using it.

What is the problem here? The problem come from the initial technological choice. At that time, SSJS was spanking new, JS itself was also new on the market. Those new technologies are often publicized by their vendors because they want to sell them, so they make it shine, appealing. Before adopting a technology like that, you have to keep in mind that people will support it long after you are retired. You need to compare what is on the market, try them, see who else is using this in production live on the Internet and opened to the world.

2.2. Double ignorance

In my opinion, the second most important cause of system legacy-ing is the double ignorance. The architect is responsible for maintainability. He must always be open-minded, accept criticism and admit ignorance to a certain point. The IT industry changes at a scary pace, so it’s safe to say that, without a proper monitoring of the technological development of the industry, one can end up with too much confidence and design software based on false truths. There is a necessity to a have a sweet balance between self confidence and self doubt so you validate your thoughts when you are challenged. This can be hard at first, even insulting at some point.

As a developer, this state of mind can also cause trouble. In the end, he is implementing the requirements. One of the worst thing a developer can do is interpret them thinking he knows what the client wants and how things should be done. The developer will implement something wrong, will use the allowed budget, fail peer review or QA and will have to revert part of the changes to implement the requirements correctly. He will not appropriately do the refactoring due to a lack of budget and leave behind messy code that other will later consider as legacy. This first breach in the code often makes other developers think that the application is not clean and they will botch the as they slowly loose interest in the application. This is a normal behavior humans adopt naturally in different spheres of life. Would you rather take care of your car if it’s brand new or if it’s an old rusty one?

An example of that is the DAO pattern. The Data Accessor Object design pattern allows to have a clean separation between the business layer and the data store. With proper abstraction, you can completely isolate both layers, but with bad implementation, you end up with the business layer being strongly coupled with the DAO layer which does not solve the initial problem. Lets suppose we have a car manufacturing system that allows engineers to design the electrical wiring of cars. Back in the days, the architect decided that there would by a DAO layer to access the car electrical part information, but today, the company asks that the application stops using it’s own database and use a third party web service to fetch the part data in the XML or JSON format. With proper abstraction, this should be really simple. A good way to determine if the level of abstraction is good enough is to try an replace the JDBC or JPA dao with a class having a Map to hold the objects, some kind of a cache, then implement an abstract factory pattern to retrieve the DAO instance based on the technology (JPA, JDBC, cache map, XML files, etc.). If you can switch the data access technology with the flick of a switch, you can consider the level of abstraction enough.

2.3. Argumentum ad antiquitatem

The appeal to tradition or common practice is another way we get a system easily corrupted. It’s not because others do things or have been doing things a certain way that it is the good way to do it. This is a common fallacy, a poor argument. In the IT industry, we have to make proofs of concept to test how things react and determine if our idea is worth implementing, and once it’s implemented, time will tell us how good our system is maintainable and expandable.

Sadly, this is something that can only be learnt by experience. When you feel like something was done wrong in a system, it’s good to take some time to analyze the said system and try to determine what the problem is. What was used in the system or what have been done in order to be in that state? This changes a lot from systems to systems because they were designed and built by individuals reacting differently to pressure based on their past experience.

2.4. Failure to keep up-to-date

Another thing I noticed in the consulting industry is the use of frameworks like Spring and Hibernate, and the failure to keep them up-to-date. With Maven of other dependency management tools like that, it should be a simple to keep those tools up-to-date. An application I had to support was in this exact state. We were using Hibernate for JPA implementation, but over the years, we did not keep it up-to-date. After a couple of years, the client asked us to implement auditing, so we analysed tools like Envers which does a pretty good job, but we noticed that the version of Hibernate we had was too old, and the cost in time and efforts to update the version of Hibernate, change the JDK version so it’s compatible with the newer version of the framework, change the JRE and the version of the web server and, finally, implement Envers was too big for the client.

Those frameworks are built and maintain by the community. They want to use the latest tools, they want challenges, to try and discover new things to facilitate the development. When a new major release is done, they often drop retro compatibility for better performance or code structure, but offers a migration strategy for one version to the other. The problem is when you skip a couple of major releases. It gets really complex to migrate the code, especially if the system is big or critical to risk the regression.

By elaborating an update plan or a modernization phase for a system, we reduce the chances of getting in a situation like the one described above. Of course it also has a cost, but the client has to invest in the system or it will otherwise slowly rot… If you don’t change the oil regularly in your car, it will not work for a long time, will it?

2.5. Unit testing

I do not want to start a war about unit testing and TDD, but I will give you my little knowledge about TDD, unit testing and legacy applications. One of the first system I maintained when I got out of school had a unit testing project with a lot JUnit tests. When I got the knowledge transfer from another guy on the team, he told me that this test suite was implemented when the system was transferred from the previous vendor to us back in 2005, but now, it’s not used anymore because it was not properly maintained over the years.

This happened again to me lately. I was transferred to another team, another client and another system with the exact same problem, at the opposite side of the country. I’m not quite 100% sure, but I think the test suite of this application was built by the previous vendor. So again, we ended up with dead code, test cases that was not reflecting the business rules and not validating anything at all.

I’m not saying that TDD and unit tests are bad. They are pretty good, in fact, passing tests along with code coverage is probably the best thing to see on Git Hub before you want to try a new open source library. But in the context of monstrous and complex systems with years of maintenance and evolution, I don’t think it’s good. You can counter argument with the fact that this is not supposed to happen, that we need to keep the test suite up-to-date, that this should be your documentation, etc.. I’m telling you, things like that happen more often that you know due to pressure and budget. In the end, the client decides what he’s paying for.

2.6. Comments and documentation

Here, I’ll take one of Uncle Bob’s argument I read in Clean Code. Comments and documentation is good to keep and share the knowledge about a system inside a team. The problem is that a system changes because the client evolves and his needs change over the years. I’m 100% sure that if you open a 10 years system’s code and look at the business tier of the system, you will quickly find a function, a method or even a whole class with comments that are not reflecting the code. JavaDoc is really good for that.

Lets take the invoice example. You have a function that calculates the total of an invoice that is the sum of all items plus the taxes. Then, the comments above the function says something like this:

	/**
	 * Sums the items price and adds 15% of taxes.
	 * 
	 * @param invoice  The invoice
	 * @return         The total
	 */
	public BigDecimal calculateTotal(Invoice invoice) {
		// Business stuff here!
	}

The problem is that this JavaDoc does not explain what the function does, it’s only paraphrasing or translating (Java to English) the algorithm. As soon as you have comments like that, you need to maintain them. If one fails, it gets pretty confusing quickly! You would need to change the JavaDoc if the function now adds, say, 15% of service fees, 10% of administrative fees and an extra taxe of 2%. Most of the time, the developers will not update the documentation. This is also true for other documentation support like word documents or a Wiki. Having a code that speaks by itself is the best documentation you can have.

3. Example of unavoidable legacy-ing

Since last year, I’m hosting Quebec city’s Global Day of Code Retreat. As part of this activity, we develop (or try to) the Conway’s game of life using TDD and challenges (no if, exaggerated abstraction, etc.). The last challenge we did last year was to start the development by TDD and extreme programming and, each 5 minutes, the person at the keyboard moved to the right and sat beside the computer while the other person moved to the left and sat at the keyboard. We did this for 45 minutes. After 2 or 3 rotations, people stopped working, they started to chat and completely lost interest in the challenge. The code was a huge mess for every single teams.

The reason why this happens is because people tend to work without considering the time constraint and start things that they will not finish, leaving the next team with an uncompleted feature. This can be extrapolated to big projects on a longer time span as the team often changes completely over the years of maintenance and knowledge gets lost. The lesson after the challenge was that you need to take time to think about the time constraint (representing the budget) and do the strict minimum to complete a task within that time, otherwise it gets quickly out of control.

4. Conclusion

There are many more ways systems get legacy-ed. Those above points are simply based on my short experience with old systems. I’m sure you can counter argument on them, but that happened and that is a fact. If things like that happens, we, as developers, analysts or architects, need to pay a particular attention them. Keep in mind that our job is to make the client save time and money by automatizing or simplifying processes.

Now you tell me. Have you ever worked with a legacy application that everyone of you teammates hated? If so, what was the problem? Tweet at me @syl20TOS and tell me your experience!

Thanks for reading!

Sylvain Cloutier

Sylvain has been programming in Java for the past 5 years, mainly in the aerospace industry, as a lead developer of complex web based aircraft wiring systems. He is also one of the organizers of the Java User Group of Quebec City and currently works as a Java developer consultant at CGI.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button