What I learnt working on large codebases – my mistakes and takeaways

Recently, while working on code that my seniors or other developers wrote at Katerra’s SaaS products. I realized that working on someone else code is like falling in to a endless rabbit hole, luckily I figured out some good engineering reads that helped me to improvise and stabilize the production code, making it more easier to manage.

Since the product utilized a Multi-tenant + Microservices architectures, most of the resources that I need were missing to run a complete app locally, just making my decisions to refactor the code would end up introducing more bugs in my code, well they had an earlier version already in production and since now I am working with the new product (which is better architectural-wise utilizing containerization using Docker) I felt the need to move to better version of the code, which I wrote in the last version. Realizing a fresh start ! Consider the below scenarios to understand what I did.

A better version

You are happily working on a feature. You add a bunch of new code so that it works according to the acceptance criteria. At some point, you decide to reuse some existing code. So you start looking around and then, all of a sudden, you end up in a different part of the codebase where the code begs for refactoring. What now? Should you address this or leave this as is?

Most of us find ourselves quite often in this situation. I recently got asked what’s the best approach here and, to me, the answer is one of the rules proposed by Robert C. Martin, which is the boy scout rule . In the context of software engineering, it comes down to the following.

Always leave the code better than you found it.

In theory, it seems pretty simple and straightforward, but let’s look at this in practice.

Why is this important

Someone might say – “if I refactor this other part of the system, I’m making changes unrelated to the feature that I’m adding” – and they would be right. But you’re not just producing a feature. You are an engineer responsible for both how the product works and how easy it is to maintain. I assume you try to make the code you’re adding clean, simple, and well-abstracted so that you don’t have to repeat yourself. At some point, you will inevitably touch some existing part of the system. All previously mentioned rules should still apply here. Otherwise, with every new functionality added, your application will be slowly degrading.

Ultimately when faced with smelling code, you have two choices. Either address this right away or count on the next developer to do this when they enter this place. And let’s be honest, option number two might never come to fruition.

Practical tips

You decided that you want to address the issue. Great! Now, before you jump into refactoring, there are a few things that work for me especially well, that you might want to consider or keep in mind.

  1. Take notes and revisit them once you’re done with a significant portion of your current work. Going straight into refactoring some other part of the system will be a distraction. You need to switch context, perhaps get familiar with what’s around. It might be better to leave a short comment and get back to it once you finish writing the code related to the feature you’re working on. I had hard time earlier to reduce the number of bugs caused due to my refactoring.
  2. Think about the return on investment. Is the change you’re trying to add simply stylistic? Or will it allow you to reason about the code more easily, reuse some existing functionality or make the infrastructure more robust? It pretty much boils down to whether the change will make your work in the future faster. Is the answer yes? Awesome, let’s move on!
  3. Assess the size in comparison with how much time you have. Will it take you minutes or hours to do the changes? Perhaps it’s more like days or even months? Do you currently have this much time? Even if you don’t have any time, you might mark whatever smells as deprecated, create a separate ticket for refactoring and make sure it doesn’t get lost in the backlog.
  4. Check if the code you’re about to interact with is well-tested. Before doing any changes in a new area that you’re entering, it’s key to understand this area and check the tests. It might be worth first writing or increasing the confidence that existing tests give you before applying any changes. The last thing you want is your good intentions backfiring at you.
  5. Don’t fall into a rabbit hole. One improvement can lead to another, which then can lead to yet another one. Make sure you set some limit for yourself so that you don’t end up forgetting what was it that you were initially implementing. The goal here is not to make the place perfect. It’s to have it a bit better than it used to be.
  6. Make it easy to review and test. Similarly, as you don’t want to break things, you don’t want your colleagues disliking you for giving them extra work to do. Clearly outline what the refactored parts are. I don’t think there’s one way to do this right, and it all depends on the scope of the changes you’re adding. You might add appropriate comments to guide other developers through the changes, which might be enough for simple things. You might deliver those changes in a separate commit. Finally, you might create a subsequent merge request to handle refactoring.

Encourage others to do the same

You’ve done a terrific job by making things a little better. But where this approach shines is when other developers share the same mindset and do this consistently. The best way to encourage other’s to do this is by sharing what you did and pointing out what in it for them. When you see that someone has done the same in their merge request, tell them that it’s awesome and that you appreciate this. Once everyone adopts the same frame of mind, the effect multiplies. You can effectively prevent the codebase from degrading or collectively address issues that are scattered all over the repository.

These incremental improvements will not replace planned refactoring processes – it still might be the only way to deal with more complex problems or some specific parts of the system. That said, it should, over time, make a positive impact on your codebase. Give it a try!

Further reading and references

Scroll to Top