I spent a good part of last month refactoring a decent portion of the codebase of our Shopify data exporter app, EZ Exporter.
There are 3 main motivations I decided to invest this much time in refactoring:
- Preparing the codebase for new features.
- Reduce maintenance cost/complexity/technical debt.
Refactoring is the process of improving the code's structure and design, which I actually quite enjoy. I'm a bit of a minimalist and I find cutting down code and finding simpler, more elegant ways to implement the same functionalities in the code very rewarding.
When you're just starting out building an MVP of your app, the main focus is to just get it working first and get it out there. While it's good to be able to anticipate potential problems in the future and prepare for it, you have to decide early on if it's worth the tradeoff of investing the additional time and delaying the product launch. There will definitely be times where you'd be glad you did it.
But you can't plan for everything. You probably don't even know at this point whether the product will get anywhere at all (or end up building a completely different product later). Often times, the better decision is to just launch this "imperfect" product quickly knowing that you'll probably need to continuously refactor the code as it gains traction and requirements change.
You may even find that once you're in production and have real people using your app, things that you thought were important during the early development days end up not mattering to your users at all. You'll probably also get requests for features you would've never thought of during development. And if you use third-party APIs, which many modern SaaS apps do, those APIs might introduce changes later that you just wouldn't have known.
So the idea is to keep things simple, launch quickly, and prepare for lots of refactoring down the road. And enjoy that process.
When I was building EZ Exporter, the idea was just to build a simple Shopify order exporter in CSV format that can be scheduled to run automatically and have the file sent via email or to an FTP/SFTP server. I finished it in about 80 hours of work. I had a bunch of tickets opened for features that I thought would be nice and resisted the temptation to work on them right away and instead waited for customers to request them first.
It turned out that was a good decision. Some of those features were indeed requested by our customers and we got our initial reviews that way as they were impressed we implemented them so quickly. And many of the ones we didn't get any requests for, we just left open which also kept the codebase smaller, making it easier to maintain. Then there were some that didn't get requested until much later and we eventually took care of some of them as well.
EZ Exporter's codebase is now much bigger and many parts of it have been refactored multiple times. I've had to refactor it when we decided to support other types of Shopify data such as products, customers, and collections. Then we've added support for other types of destinations such as a Dropbox or Google Drive folder and to an Amazon S3 bucket. And recently we've added support to be able to export to Excel formats as well, which required another refactor so we can quickly add support for even more formats in the future.
We've also gotten to a point now where optimizing parts of the code to reduce resource usage makes sense and required refactoring. It didn't make sense early on with a smaller userbase as the servers were way underutilized. But once you get to a certain scale, it starts to matter as the savings add up. In our case, the app is backend heavy where many jobs run concurrently across multiple servers. So optimizing certain things allows us to run more concurrent jobs per server, reducing our server costs.
Refactoring is a long-term strategy. The ROI may not be apparent until much later in the future, but could potentially be very big. It could be much harder and costly to do later as the codebase gets more complex and the technical debt compounds. It's good to think about it early but don't go crazy with it, the codebase will never be perfect and you could end up spending way too much time on it than you probably should. It has to make business sense too and it's up to you to decide what's a good balance.
I usually like to think about it when I'm about to add new code or need to update existing ones. I want to see if there's anything related to that code that I can cut down or simplify. This way, I can test both the new code and the related code at the same time. I try to avoid making changes to code that's completely unrelated to what I was currently working on in order to avoid potentially introducing bugs unnecessarily.
I like to deploy them in smaller batches as well. This way, it can run in production for a bit and in case there's any weird issue it will be easier to fix/debug or roll back if needed.
As the developer and maintainer of the app, it feels good to know that your last commit left the codebase in a better shape than before. It's very motivating to see that improvement over time as you know that next time you need to add new features or make changes, the improved codebase will make it much easier and more enjoyable to work with.