A sepia image of old pressure valves

Everybody hates working on legacy projects, myself included. As fate would have it, one landed in my lap recently. While working on it didn’t make me hate legacy projects any less, it did help me get a deeper understanding of the processes and practices we use today. 

I am proud that I am a part of a team that uses most of the best practices: 

  • Writing clean code and automated tests 
  • Participating in pull requests and tasks reviews 
  • App is in production within the same day of merging into the master branch 
  • Agile is well adopted 

 It’s not perfect. Pull requests sometimes contain trivial suggestions and discussions. The ops team usually messes something up (or at least that’s what we devs like to think). Our product owner occasionally wants us to fast-track some ‘easy’ features… again. But all in all, things are pretty good. 

A trip to the Ant museum 

Since our team was performing well, our velocity was borrowed to another product, managed by another part of our company. We weren’t thrilled about it since the project uses an older version of Java, and the code wasn’t written the way we would do it.  

The job was to add a few metrics – simple ones, like whether the app is running and for how long, is it processing data (fast enough). The project itself was in maintenance mode, and no new features had been added in a while. Since we are accustomed to adding metrics, this should have been a piece of cake. 

Rolling up our sleeves, we first noticed that the project had been built using something very old - Ant  build files. They are big XML files that contain every piece of information on how to build the project in question. For example, you need to specify that the project needs to be compiled, tested, and packaged. All details like source, target, and resource location must be explicitly configured within those files. This used to be pretty common for many programming languages. You wrote a build file once, copied it on each new project, and then changed it until it worked for the new project.  

Here’s what an Ant build file for a Hello World project would look like:

<project>

    <target name="clean">
        <delete dir="build"/>
    </target>

    <target name="compile">
        <mkdir dir="build/classes"/>
        <javac srcdir="src" destdir="build/classes"/>
    </target>

    <target name="jar">
        <mkdir dir="build/jar"/>
        <jar destfile="build/jar/HelloWorld.jar" basedir="build/classes">
            <manifest>
                <attribute name="Main-Class" value="oata.HelloWorld"/>
            </manifest>
        </jar>
    </target>

    <target name="run">
        <java jar="build/jar/HelloWorld.jar" fork="true"/>
    </target>

</project>

There must be a more convenient way to manage that, right? Well, that’s why the idea of convention over configuration came to life, suggesting that developers only need to specify unconventional aspects of the application. Modern build tools have adoptedthis paradigm by providing developers with defaults that can be overridden if needed. That’s why most java source files are in src/main/java source, while the compiled classes are in the target folder—no need to specify this over and over. 

That got us thinking about whether this approach would be helpful in our current projects. We had a huge application properties file, with most values having the same value (e.g., application port). Could we apply the same principle to our application properties? Most properties could have defaults, and our properties file wouldn’t be as large. 

Things we take for granted

Back to our legacy project – we managed to build and package our application! The tedious part was behind us, and we could start coding. But how do we embed our metrics component into the legacy code base? We took this for granted because our application framework usually handled it.  

Now, what would be the best way to inject our metric components into various parts of the legacy code? Singletons! Well, it seemed the easiest way, at least, but the community considers them anti-patterns. Why? Isn’t our beloved XY framework relying on singletons? If not, what does it use? What is dependency injection? How does it function under the hood? 

These questions got us thinking about basic concepts that we took for granted. Using singletons wasn’t exactly a bad idea in this case since most of the code wasn’t covered with unit tests. Nevertheless, we couldn’t have a good night’s sleep unless the code was immaculate. We tried a different approach and ended up with decent and simple code – no singletons, no new abstractions. 

The limited role of developers 

The only thing left was to deploy it so that we could test it. This was a problem, of course – in this case, we were not the ones deploying it or testing it. Deploying had to be done by the ops team, and testing by the test team. Why couldn’t we, as developers, manage the whole feature from start to production instead of opening tickets and waiting for other teams before we could close our task?

Firstly, we couldn’t avoid manual testing since much of the code wasn’t covered with tests. We also couldn’t deploy the application ourselves since the infrastructure didn’t allow us to.  

This had us thinking about the reasons behind the separation of duties and how our current approach is better. Seeing that our lead and cycle time on this project was significantly higher than usual (it took us weeks to deliver something we usually deliver in several days), the evidence was strongly in favor of that.

Learn outdated practices to understand current ones

At the end of the day month, our metrics were up and running in production. My feeling about legacy projects hasn’t changed – I still hate them, and I am not expecting you to hate them any less.  

We couldn’t change the project we were assigned to, and these were the cards that we were dealt. What we could change is our attitude towards the legacy project. Instead of feeling resigned, we saw it as a place to ask questions and learn.  

It taught us how things were done before and why they are done differently now. Instead of just knowing the best practices, we gained first-hand experience with the history behind them.  

Once you start having this deep-rooted knowledge, other developers will recognize this and trust your knowledge and expertise. If you want to be that person, you better be ready to dig into some legacy projects.