Dealing With Legacy Code, A Worthwhile Endeavor

3i Editor
Team 3i
Published in
8 min readJul 6, 2023

We would like to reintroduce an insightful article written by one of our former software engineers who bravely tackled the challenges of working with legacy code. Although Sook has moved on from 3i, his experiences and lessons learned still hold great value for our present and future endeavors. We present to you the original article, titled “Dealing With Legacy Code, A Worthwhile Endeavor,” which sheds light on the journey of unraveling and improving a complex codebase. Let’s delve into the engineer’s firsthand account and discover the wisdom he gained along the way.

Prologue

As a software engineer, every time we join another company we’ll be facing code bases that are written by other people. And whenever we’re introduced to a new software project like this, we tend to become excited and passionate to learn everything that’s in there — ready to jump right in — at least initially.

However, things go south pretty fast if there’s no one in the company that could get you up to speed and/or guide you to increase your understanding about the project.

Worse yet, what if there’s practically no internal documentation or written information about it?

In other words, imagine a situation where there are only you and this source code at which you’re staring that’s mostly alien to you. Pretty chilling, right?

Well unfortunately, that’s kind of what happened to me when I first joined 3i. A sole developer who had written most of the code for the project, in which I was going to be involved was long gone already.

So.. what do I do?

I was bold enough to go straight down the rabbit hole and started digging around the source code (remember the initial passion?) but often found myself being completely puzzled and dumbfounded. The primary reason reading through the code was particularly painful had to do with the fact that, the main technology used all across the project, was not something I was familiar with at all, at lease at the moment.

Let me give you a little more context.

I was hired as a frontend developer, and this project was a javascript based project that ran in a browser. So far no problems, right?

Except there is.

It was a web application that makes heavy use of WebGL technology (actually mostly indirectly through this library called three.js), which is, I’d dare to say, something completely different than, well, making a website.

And making web pages (or more like web apps) was what I was most familiar with, as a frontend engineer before joining 3i.

So yes, seemingly bright future was ahead of me.

Identifying Code Smells

As I was going through this painful cycle of digging the source code and searching unfamiliar concepts online day in and day out, I slowly began to realize that it was not necessarily this WebGL stuffs alone that were making my understanding improve slowly, but it actually had more to do with the bad coding practices that were present all across the code base.

I don’t mean to say that I’m more proficient at javascript or at coding than the original author of this project, but I hated analyzing this source code so much; how do I say.. it was a gut-wrenching experience.

Let me list some of the stuffs I’ve found gut-wrenching, to give you a better idea of what I was dealing with.

Here they are:

  • the structure of an outdated version of a similar open source project were copy-pasted verbatim without leaving any reference or acknowledgement to it.
    - the structure was never maintained or improved after the initial copy-and-paste
    - a lot of unused parts of the structure are still in there as a result
  • some external dependencies are not managed by a package manager.
    - they rather sit inside a directory called lib
    - they have never been properly updated after the initial copy-and-paste
    - some of the modules are even internally modified by my predecessor
  • heavy reliance on magic numbers / constants.
    - without any reasoning as to why a particular number / constant was used
  • tight coupling with the projects consuming this project.
    - internal states are not properly protected and being modified / mutated from outside
    -
    some internal 3rd-party dependencies are also exposed and used outside
  • poor abstractions / encapsulations commonly found in internal modules.
    - modifications of a module’s internal states happen all over the place
    - they are mostly just separated into different files, not separated by concerns or functionalities
    -
    some modules / methods are just too long; spanning a few thousand lines easily
  • a lot of duplicated / unnecessary logics that undermine performance
    - certain computation logics are not well-thought-out and ended up being terribly inefficient
    - the existence of unnecessary multiple loops which could be reduced to one
  • useless comments and commit messages
    - something like // Most important method
    - one of the commit message was actually “commit message”

I mean.. don’t get me wrong. I’m not saying that I follow absolute best practices all the time when I code, and sometimes I, too, make some sloppy decisions especially when I’m under time pressure.

But, I could find no evidence of careful design considerations, concerns on possible implications from choosing one way over another, or efforts to maintain good coding conventions, in the code base.

And note that all these things I’ve been talking about so far in this section are not especially relevant to WebGL technology; rather, they apply to any software development practice.

But the ranting is no more.

If I found the legacy code repulsive and impossible to work with, I should simply come up with a better version and replace it.

That’s right. it was time to act.

The Rewrite

To fill out this section with as much details, I sort of had to remind myself what the initial progress was like by going through my archived daily notes months and months ago. And I stumbled upon this diagram that I drew.

Something I drew a long time ago

You don’t actually have to read anything in the diagram and try to make sense of it. I’ll summarize it for you.

I was basically suggesting we needed to invest time on a new implementation of this legacy project with all seriousness while maintaining (minimizing) activities in the current legacy code, and when the new implementation could handle most of the existing features, we migrate to the new one.

The beginning of the rewrite was very humble, which started to happen roughly two months later I drew the above diagram. It was not something like, I finally announced that I’m going to work on the rewrite for full time from now on, but it was more like my small attempt to replicate the current behaviors with my improved understanding of three.js over time, mainly to better understand the legacy code.

While iterating through the cycle of

  • identify behaviors and features from our app
  • try to replicate them with my three.js knowledge alone
  • reference back to the legacy code if I get stuck or fail

My understanding of the legacy code and of three.js both had increased significantly.

This iterative workflow went on about three months, and now my temporary playground project had become larger and larger, and I could feel that the dependencies among the modules I’d made were building up and the project in general was becoming harder to maintain.

It was time to graduate from the playground, and start thinking about more robust project structure.

At the moment I was also playing with TypeScript, and while browsing its documentation I stumbled upon this design pattern called mixin. And it looked like a good fit for my situation because the main reason I was searching for well-recognized design patterns that were proven to be effective, was to break down big classes into smaller, more manageable chunks that handle independent tasks.

So I went on with this mixin structure for a while even though in fact, sometimes I felt like I was forcing this pattern to situations in which it was not really appropriate or efficient.

The Rewrite of Rewrite

My poor knowledge of software design inevitably led me to try a bunch more patterns that I thought made sense.

Eventually I’d settled with an event-based pattern with a central store where each module can read other modules’ exposed states and dispatch events to trigger state changes indirectly.

The structure will probably change again in the future as my understanding and knowledge of software design improve.

But that constant revision or refactoring of previously written code is, I believe, an essential part of building a robust, flexible and long-living software.

So yes, I’m planning to rewrite my rewrite over and over again in the future.

What I’ve Learned

Writing a good software is hard, whereas writing a bad one is noticeably easier because you make significantly less mental effort to think about possible implications or alternatives of a particular decision you make.

And when those bad practices sufficiently accumulate, we’ll be put in a situation where no further improvement can be made to the software. At that point, you might as well think the software is dead.

Ideas, designs, requirements, users’ demands, they all change at much faster pace than a software can keep up with.

And to be able to continuously deliver a product in this fast changing environment, the software must implement a robust structure and a good design, to which every good software team should pay attention.

So there you go. That’s what I’ve learned while working with the legacy code.

It showed me what kind of a disaster could happen when an accumulated technical debt of a bad software is not properly addressed. It slows us down tremendously when we need the most speed to move forward.

It taught me how important it is to have a good structure and design when building a software. I’ve become more vigilant about keeping the code quality high, and I won’t sacrifice the quality for speed, ever.

Epilogue

I am now confident enough to say that a lot of the problems — stuffs that were mentioned earlier — in the legacy code base no longer exists or at least was dealt with in the rewritten version.

In addition to that,

we now use TypeScript,
we now have a better project structure,
we now have better performance,
we now have a well-written documentation,
and last but not least
we have us, our team, the last bastions of refactoring legacy code who understand the danger of said bad practices better than anyone and strive for best practices.

All right, that was all from me. I gotta go.

I have some refactoring to do.

If you are someone who thrives on the challenge of untangling complex code and transforming it into something exceptional, we invite you to join us at 3i. We value the power of collaboration and continuous improvement, and we’re always on the lookout for passionate software engineers who are eager to make a difference.

While you’re here, don’t miss out on our other insightful articles that delve into various aspects of software development. From coding best practices to innovative technologies, our knowledge-sharing community is committed to helping you expand your skills and stay at the forefront of the industry.

Together, let’s shape the future of software engineering and embark on meaningful journeys of code exploration and transformation. Join 3i and let your coding prowess shine!

--

--