What to do before refactoring
Seven critical things to keep in mind before starting your next refactoring project.
Hi Friends,
Welcome to the 140th issue of the Polymathic Engineer newsletter.
At some point, every programmer needs to work with code that makes them cringe. It could be a long function without any comments or a class that does everything except what its name suggests. Your first instinct might be to delete it all and start over with something clean and elegant, but this is not always the best solution.
The urge to make code better is normal and often right. Good refactoring makes code more maintainable, easier to understand, and less likely to have bugs. However, poorly planned refactoring can waste weeks, introduce new bugs, and leave you with worse code than what you started with.
The difference between a successful refactoring and a failed refactoring often comes down to preparation and approach. This article walks through key considerations that can help you and your team save significant time and frustration.
CodeRabbit: Free AI Code Reviews in CLI
This issue is offered by CodeRabbit, the Free AI Code Reviews in CLI. CodeRabbit CLI is an AI code review tool that runs directly in your terminal. It offers intelligent code analysis, catches issues early, and integrates seamlessly with AI coding agents like Claude Code, Codex CLI, Cursor CLI, and Gemini to ensure your code is ready for production before it ships.
Enables pre-commit reviews of changes, creating a multi-layered review process.
Fits into existing Git workflows. Review uncommitted changes, staged files, specific commits, or entire branches without disrupting your current development process.
Reviews specific files, directories, uncommitted changes, staged changes, or entire commits based on your needs.
Supports programming languages including JavaScript, TypeScript, Python, Java, C#, C++, Ruby, Rust, Go, PHP, and more.
Offers free AI code reviews with rate limits so developers can experience senior-level reviews at no cost.
Flags hallucinations, code smells, security issues, and performance problems.
Supports guidelines for other AI generators, AST Grep rules, and path-based instructions.
Understand What You're Working With
Before changing anything, spend some time getting to know the current codebase and the tests that cover it. Start by reading the code and trying to understand what it does and why it was written that way.
Every piece of code has a story, and understanding that story helps you to make better decisions about what to keep, what to change, and what to throw away.
Look for strengths in the current system that you want to keep. Maybe the way errors are handled is very robust, or there is a clever optimization that makes things run faster. If some things could be better, that doesn't mean everything needs to change.
Pay attention to the tests that are already out there. What kinds of situations do they cover? What types of edge cases do they look for? Tests can reveal requirements and business logic that aren't immediately apparent from the main code.
This investigation phase helps you avoid one of the biggest mistakes in refactoring: throwing away valuable knowledge because you don't recognize its importance.
We all think we can write better code than the existing one, but we then end up with something that's no better, or sometimes worse, than what we started with. Taking time to understand what you're working with is the best way to ensure your refactored code actually represents an improvement.
Resist the Temptation to Rewrite Everything
When you look at messy code, it's tempting to think about rewriting everything, but this is rarely the solution.
Even if the current code looks ugly, it works, and it has already been tested, reviewed, and used in production. It contains workarounds for edge cases you haven't thought of and fixes for bugs that took time to discover and solve.
When you throw away existing code, you also get rid of all the information that came with it. The new code you write might have the same strange bugs that were fixed in the old version, and it’ll take you weeks to find problems that have already been fixed.
This doesn't mean you should never replace large chunks of code, but make sure you have a compelling reason beyond "I think I can do it better" or "This code is hard to read."
Make Changes Incrementally
When you modify hundreds of lines of code at once, it's hard to know what impact you're having on the system. Small, incremental changes are much easier to manage and much less risky.
Incremental changes let you get feedback quickly through tests and real-world usage. If something goes wrong, it's easier to figure out what caused the problem when you only changed a small piece at a time.
No one wants to see a hundred failed tests after making a change. That makes people frustrated and under pressure, which can result in bad decisions and rushed fixes. A couple of test failures at a time is much more manageable.
My favorite approach is to make the smallest possible change that moves you in the right direction, then verify that everything still works. Run the tests. Check that the system behaves as expected. Only after you are confident that the change is working, do you move on to the next improvement. This approach takes patience, but it builds confidence.
Preserve and Build Upon Existing Tests
Tests are one of your most valuable assets during refactoring. They let you know when you have broken something, giving you confidence that your changes are correct. But many developers make the mistake of throwing away existing tests too quickly.
Tests are living documentation of how the production code should behave. When you throw away tests without understanding them, you throw away that documentation.
Before deleting any test, try to understand why it was made. Even if some tests might not seem relevant to your new design, they often capture edge cases or business needs that aren't obvious from the code alone.
When you change code, make sure that the existing tests still pass. If a test fails, don't immediately assume the test is wrong. Try to understand what the test was protecting against and whether your new code handles that case correctly.
If you need to modify tests to work with your new code, be careful. Make sure you are still testing the same behavior, just in a different way. If you decide you need to get rid of a test completely, write down why. Write a note or make a ticket to clarify what the test checked and why it is no longer needed.
Check Your Motivations
Before starting any refactoring job, ask yourself why you want to make these changes.
"The code doesn't match my personal style" is not a good reason to refactor. Neither is "I think I could do this better than the previous programmer." These motivations are driven by ego, not by genuine improvements to the existing system.
Just because code looks different from how you would write it does not mean it needs to change. There is a risk with every change, and that risk needs to be weighed against real rewards.
Good reasons for refactoring include: the code is hard to understand and modify, has known bugs that are hard to fix with the current structure, or is really slowing things down.
Good refactoring makes the system objectively better. You should reconsider whether the refactoring is necessary if you can't identify specific, measurable improvements.
Evaluate Technology Changes Carefully
One of the worst reasons to refactor is that you want to use the newest and greatest technology. Thoughts like "This code is so out of date" or "We could do this so much better with the new framework" are appealing, but they're not usually good enough to support a major refactoring. New technology feels cool, but it also has risks.
Before you start writing everything over in a new language or framework, you should carefully weigh the pros and cons. Will the new technology really make things work better, be easier to keep, or make people more productive? Or does it just seem more up-to-date?
Sometimes, technology changes are needed. You might be using something that isn't supported anymore or has significant security vulnerabilities. But make sure that you have real business reasons for making the changes, not just the desire to work with something more interesting.
Accept That Refactoring Can Fail
An uncomfortable truth is that refactoring doesn't always make things better.
Sometimes, despite your best intentions and careful planning, you end up with code that's no better than what you started with. Sometimes it's worse. I've seen several failed refactoring attempts over the years. Teams spent months rewriting working systems, only to end up with code that was harder to maintain, more buggy, or less performant than the original. It's not pretty, but it's human.
This doesn't mean you shouldn't refactor. It means being realistic about the risks and having a plan in place for when things don't go as expected.
Be prepared to stop if the refactoring isn't working out. Sometimes the best decision is to abandon a refactoring project and stick with the existing code, even if it's not perfect.