What Should Stay and What Should Go: A Practical Guide for Refactoring Code


Let’s be honest: as developers, we’ve all written bad code at some point. Whether it’s due to deadlines, unclear requirements, or just oversight, code quality often suffers. Eventually, we’ll face the inevitable—going back to refactor or remove that bad code, which is especially true in startups where speed is preferred. (Deep breath—it’s normal.)

This article is here to help you make smart decisions when refactoring code, so you will have a clearer idea about what should stay and what should go. Sometimes code could require a complete redo, other times it just needs a partial change, and occasionally, it’s better to leave it alone (embrace it). Let’s review some common coding issues and see what we can/should/must do.

Case1. Generic or Unclear Names: Should They Stay or Go?

You’ve probably seen it before: var1, numX, or temp. These kinds of generic names seem harmless when you’re in a rush, but six months later, when you’re revisiting the code, they’ll feel like an evil puzzle, a great source of depression, anxiety, and quitting. Clear naming is essential because it significantly reduces the amount of time and energy (and therapy costs) required to understand what the code is doing.

So, what’s the plan?

  • If these names are spread in your project: It’s time for a full renaming. Meaningful names make your life easier in the long run—refactoring tools can help speed up the process, especially in a large codebase. Once you rename things properly, you’ll realize that the code actually starts making more sense, almost as if it’s helping you out.
  • If these names exist in small, isolated parts of the code: This is one of those cases where renaming is useful but doesn’t need to be urgent. You can leave it for now, but next time you touch that piece of code, go ahead and fix it. Your future self will be grateful.
  • If it’s legacy code that hasn’t been touched in years: Honestly, you might want to let this one stay. As long as it’s stable and not causing issues, renaming might just introduce bugs. Instead, leave detailed comments or documentation explaining what the names refer to. Think of it like leaving a note for future archaeologists.

Case 2. Complexity: When to Refactor and When to Walk Away

Let’s talk about complexity. We’ve all been guilty of over-complicating things at times (our beautiful over-engineered solutions). Maybe it’s trying to solve too many problems at once, or just getting too fancy with the code. The result? Code that’s hard to understand, hard to modify, and a great candidate for bugs.

What should you do?

  • If the code isn’t too complex and easy to grasp: Rewrite it. Simplify the logic as much as possible. You’ll often find that breaking things down into smaller, more manageable pieces helps with both readability and debugging. The simpler it is, the better.
  • If it’s genuinely complex but necessary: Here’s where you need to be a bit cautious. You don’t want to introduce bugs by messing with important functionality. Instead of a complete change, go for a gradual refactor. Break down the complexity step by step each time you touch the code, or focus on simplifying one part of it at a time. Over time, it’ll get easier to manage.

Case 3. Magic Numbers: The Hidden Trap

Magic numbers (or strings) are these values that appear in the code, here and there such as value boundaries, that make no sense until you spend thirty minutes trying to figure out why 36.5  and not  36.6 is being used. They’re the silent killers of code clarity, and they should almost always go.

So, here’s what you do:

  • If the magic number represents a critical value: Replace it immediately with a named constant. Give it a meaningful name, like ITEMS_PER_PAGE or TABLE_NAME. It instantly clarifies why that number is important, and if you ever need to update the value, you only need to do it in one place.
  • If the magic number is only used in a specific part of the code: It can stay, but it still needs a name. Even if it’s isolated, a named constant will always make things clearer. So, while the urgency is low, it’s still a good habit to get rid of those hard-coded numbers.
  • If it’s a well-documented, obvious value: Sometimes, a number is just a number—like 0 for resetting a counter. In these cases, it can stay, but this is the rare exception. As long as its use is clear and understandable to any developer, you’re fine. Just don’t overuse this reasoning as a crutch to avoid refactoring.

Case 4. Error Handling: When It’s Not Enough

Error handling is one of those things you don’t think much about (but you will). But how we handle errors can make or break the reliability of our systems. Ignoring or poorly managing errors leads to unexpected crashes, bugs, and an overall unstable system.

Here’s how to handle it:

  • If you’re catching generic exceptions everywhere: This is a dangerous practice. Catching Exception in every block and logging a vague message isn’t helping anyone. Refactor these cases to handle specific exceptions, and make sure you’re logging enough details to actually debug the problem.
  • If you’re swallowing errors silently: Get rid of this immediately. Silently swallowing exceptions without logging them creates a ticking time bomb. You should at least log the error or handle it gracefully with meaningful error messages for the end user.
  • If the code is stable and error handling is in place, but not perfect: Silent the perfectionist voice. If your error handling covers most cases and works well enough, let it stay. You can always improve the logging or user messages later, but if it’s not causing major issues, it’s better to focus on bigger problems.

Case 5. Dead Code: Time to Let Go

Functions, classes, or variables that were useful once but are now just taking up space. Keeping it around the codebase can lead to confusion. If it’s not being used anymore, it’s time to say goodbye.

What should be done?

  • If the code is clearly unused: Delete it. There’s no reason to keep dead code hanging around if it’s not part of the current system. It’s just taking up mental real estate for you and anyone else working on the project.
  • If it might be needed again: Sometimes, you’ll run into dead code that was part of a feature you plan to bring back in the future. In this case, leave it in but add a comment explaining why it’s still there and when it might be used again.
  • If it’s deprecated but stable: Move it into a separate, archived module. That way, it’s not messing up the main codebase, but you can still retrieve it if needed.

Case 6. Code Duplication: Don’t Repeat Yourself

Duplicating code is often the result of cutting corners during development. But duplicated logic is dangerous because if you ever need to update it, you’re forced to do it in multiple places, which can easily lead to inconsistencies, bugs, the end of the world, etc.

What’s the plan?

  • If the code is duplicated across multiple places: Consolidate it. Refactor the duplicated logic into reusable functions or modules. This is one of the simplest ways to reduce tech debt and create a beautiful maintenance path.
  • If the duplication is small and localized: You can leave it for now, but it’s something to tackle when you’re next refactoring that area. Duplicating small snippets isn’t the worst sin, but it still adds to your future workload.
  • If the duplicated code varies slightly: Sometimes code looks similar but performs slightly different tasks. In these cases, merging them might introduce more complexity. Leave them as-is but document the reasoning behind the duplication so it’s clear to others.

Critical Code: When to Refactor and When to Leave It Alone

Critical code is critical code, just the bravest and experienced developers would want to touch it, so let’s do nothing for now… Ok, let’s do something. How do you decide whether to refactor or leave it alone?

Refactor it:

  • If it’s becoming difficult to maintain or scale: If the logic is messy and new features are hard to implement, it’s time for a refactor. Do it incrementally, with deeply testing at each step.
  • If the business needs have grown: When the code can’t handle increased traffic or new requirements, refactoring is necessary to align with new demands.
  • If it’s using outdated technologies: Refactor immediately if the code depends on deprecated libraries or frameworks, to avoid security risks and compatibility issues.

Leave it alone:

  • If it’s stable and well-tested: Don’t touch it if it’s working decently and doesn’t need regular changes. Unnecessary refactoring can introduce new bugs or the apocalypse.
  • If it’s complex but reliable: Critical code handling complex processes, it should stay if it is working well.You can mitigate the risk by documenting and testing.
  • If the risk is higher than the potential benefits: Sometimes refactoring a perfectly functioning piece of critical code is more trouble than it’s worth. Strong test coverage and monitoring are safer options.

Conclusion

Great job! I hope you’ve learned something new or reinforced your knowledge about the refactoring process. I highly encourage you to be careful but brave when maintaining a codebase. As you can see, you can save a lot of time, significantly increase the code quality of an application through proper refactoring, and reduce risk by analyzing multiple scenarios.

I’ve tried to be impartial. From my perspective, I always try to refactor whenever I can, because by doing so, you will understand the code better and improve maintenance. Yes, I’ve caused multiple bugs during my career as a result of refactoring; however, the majority of them have been easy to solve, mainly because I gained a deep understanding of the process. Over time, you’ll acquire the ability to reduce the probability of errors when refactoring, allowing you to enjoy the benefits of high-quality code.


Leave a Reply

Your email address will not be published. Required fields are marked *