Rewriting a large piece of software from scratch is usually not a good idea, regardless of how messy or problematic the original/legacy code is. The reason is that you never know how long the rewrite is gonna take, what unexpected problems you might run into or even if your new solution is gonna be any better than the old. You might end up spending several months on refactoring without any possibility of releasing anything into production until the rewrite is complete. Remember that all code that’s written but not released is a huge cause for concern as its the perfect breeding ground for hidden bugs.
If you are not convinced, here are some other reasons why you should be careful with mindlessly throwing away legacy code:
One useful strategy for mitigating the inherent risk of refactoring is to isolate the functionality you want to rewrite into smaller chunks. This will enable you to release small pieces code into production continuously, meaning that you can verify that what you are building is actually working and giving you the possibility of backtracking or changing course at any point in a very agile way. In case you hit a dead end, you can always fall back to your latest working version without having to discard weeks or months worth of work. More often than not, it will also force you to come up with a more robust software architecture as well!
Some architectural patterns can be used in order to execute this theory into practice. They are all very similar and/or overlapping, with the main idea being to be able to shorten the release cycle in order to get quicker feedback and reduce overall risk.
Hide your legacy solution behind a proxy component. Useful while migrating backend code. In the first iteration, the proxy will simply forward all calls to the old system. Later on the proxy will take on more and more responsibility, one step at a time, until the old system at some point becomes completely superfluous.
Put your old code inside a shell that’s running the new framework you have chosen to use. Rewrite the old code one small piece at a time and move it out into the outer layer. When all code has been migrated, the inner layer can be deleted. The Exoskeleton pattern, which Martin Fowler calls the Strangler pattern], could be a useful strategy if for example porting an existing app to another framework.
Some things to keep in mind while using the Exoskeleton pattern:
I hope you will think twice before you utter the dreaded words “let’s rebuild it from scratch” next time. It is extremely important to tackle technical debt at a continuous basis, but problematic legacy code needs to be refactored responsibly, one piece at a time, rather than blindly. Please don’t risk taking on more than you can chew.
This being said, you should never be afraid of deleting code that is not being used (for instance commented-out or “dead” code that cannot be reached at runtime). Hopefully you are using a version control system like Git, meaning you can always retrieve the deleted code again if you (against all odds) might need it back.