Rewrite or Refactor?

All Articles Culture Data Management Level 12 News Python Salesforce Software Development Testing

When inheriting a poorly-written codebase, it's instinctive to want to scrap the whole thing and rewrite it, rather than deal with the headache of working in the software equivalent of a hazmat zone.

I was tasked with rescuing a cross-platform GUI application. The support team dreaded new releases, as significant bugs and quality escapes were making customers unhappy. A rewrite wasn't a realistic option, so I began to rebuild the application from the inside out.

man siting at table using macbook
Pexels

Sometimes, a codebase is built on a broken foundation and can't be salvaged. For this project, however, "rebuilding the plane while flying it" allowed us to continue shipping improvements without waiting months for a completed rewrite.

I accomplished this using area modules, automated testing, and consolidating OS-specific code.

Modules

A giant, 3000-line object with over 70 class variables contained most of the non-GUI logic. To clean this up, I began to tease out code for functional areas, such as API access or user management, into separate modules1. Over time, I ended up with over 20 modules.

There were no automated tests, so I added tests for each module. At first, the tests would merely assert that the code worked as before. Once the tests were in place, bugfixes became much easier.

Testing

I would write a new test that expected the correct behavior, then update the affected module, making the test pass. I also had the confidence to clean up poorly written code, because the tests verified that my changes preserved the expected behavior.

To speed up the release process, I added a CI pipeline to run the tests. Additionally, commits to the main branch would build and sign the app installers for the three supported platforms.

Generic Interfaces

Another headache in the original code was the many scattered conditionals handling platform-specific behavior. I replaced the conditionals with generic interfaces, with per-platform implementations, to make OS-specific code invisible to callers.

Refactor for Results

As I applied the above techniques, quality issues, latent bugs, and security issues were rooted out of the codebase. New releases were mostly pain-free.

I successfully advocated for a more frequent release cycle, allowing users to receive bugfixes and new features every few weeks rather than every few months.

Instead of being burned by buggy releases, the support team received fixes for their most pressing issues.

When a rewrite isn’t possible, refactoring is a viable option to improve the codebase, overall application, and user and support experience.


  1. Although a bit dated, I found "Working Effectively with Legacy Code" by Michael Feathers a helpful reference during this process.

Originally published on 2025-02-14 by Ben Chopson

Reach out to us to discuss your complex deployment needs (or to chat about Star Trek)