Refactoring the core functionality of a piece of code can be a terrifying prospect. Recently on our team, we have been refactoring some of the core features of mCent to make it possible to run self-service campaigns. We’re super excited to be able to make it easy for individuals to be able to run their own campaigns on mCent, but there is a lot of work involved to make it happen.
Major Key 1 – Testing
I can’t thank my team enough for the tests covering the existing code. Some of the tests aren’t the cleanest. Some tests are truly unit tests. Some tests test a few layers of logic. Some tests are integration tests. But having a multi-layered approach to testing really helps when refactoring, so that you know what behavior to expect. Sure, a lot of tests may mean more work needed to make them work post-refactor, but the effort is well worth it.
Major Key 2 – Refactoring in Small Bites
When approaching a large chunk of code to refactor, it’s tempting to attack the whole problem at once. While there are a few reasons refactoring in one go could go poorly, I think the largest problem is communication with teammates. If you are looking at a pull request, it is much easier to understand the intent behind specific changes if it is not a 2,000 line diff.
Also, consider the time that it takes someone to give a huge PR a thorough review, it can be a large part of the day. Smaller PRs make it easier for other people to understand the intention of the refactor.
Major Key 3 – Don’t Refactor In Place
This may be the most important major key. If you refactor code in an append only manner, then you leave a large safety net to test out the refactor in a controlled manner. Here at Jana we heavily use our experiment framework to roll code out. We randomly assign an editable percent of our members to variants: one bucket is the control behavior, and the other is the refactored code. From here we are able to verify that our code is indeed working in production in the way we expect it. We analyze different metrics and compare the results, making sure that the business logic truly did stay the same.
If anything goes wrong (and if you’re refactoring a large enough piece of code, something almost definitely will), we are able to quickly flip all traffic to the control, add tests that would’ve caught the issue, and release a fix.
However, refactoring in an append only manner can lead to mass confusion if you don’t end up cleaning up the old code. Then all of a sudden you have two code paths that do the same thing. If someone updates the old code, you could be left with an inconsistent state. It’s extremely easy to forget this step! You update the code, make sure it works, then move on to the next feature, leaving old code to languish, unloved by anyone.
Hopefully these 3 major keys will help you with your next refactoring project! Let us know if you have any other tips that you find useful.