Road to Refactoring
Over the years, I've come to work on many medium to large scale code bases. Most of these have organically grown over time, some of them being full of Lava Layers. Doing a refactoring in those code bases is often not trivial. Incidental complexity is high, test coverage is low. There are more features than you can count.
Also, where do you start? There are so many things you'd like to tackle and do differently, but everything you touch has the potential to introduce regressions.
In this series, I'm trying to list some of the things that I've done to make refactorings a success rather than a disaster. This is by no means an exhaustive lists, and is heavily biased by my personal experience. Further, it likely doesn't apply to your side-project or early start-up, so as usual, your mileage may vary. That being said, here we go with the first tip: 🚀
You get a bug report, highest prio, customer is escalating, account management is permanently asking: "what is the ETA on this, what can I tell the customer?"
You look at the code and analyze the issue. Maybe it's in an area of the code base that hasn't been touched for a while, or maybe you've not looked at it in a longer time.
Likely, you won't like what you see. Software patterns, especially in the frontend world, can evolve rapidly. Even if you start with something new, chances are you would do it differently in a couple of months.
Maybe you see a React Class Component that fetches in componentDidMount. Wtf, we've moved to react-query half a year ago, what is this? Or maybe there are some global styles or deprecated components being used. Oh, and this dependency could really need an update...
Scout's principle - time to clean up this mess...
There's a time and a place for everything, but this is not the time for a refactoring. You don't want to prolong the actual fix. As an engineer, you are a problem solver, and your only goal here should be to fix the actual problem. Also, you might introduce another regression, and code reviews will take longer if you add unrelated changes.
That doesn't mean we should compromise on quality. Even in those situations, we still:
- Create a branch (no direct merging to main)
- Write proper commit messages
- Get the required amount of reviews
- Run the CI pipeline
And make sure that all other quality gates that we have set up still pass. We surely want a fix as fast as possible, but not at all costs.
This is the flow I usually take when getting a bug report:
This is the best flow:— Dominik 🇺🇦 (@TkDodo) November 22, 2021
🐞 find a bug
🕵️♂️ investigate it
⇆ reproduce it
🧪 write a failing test for it
💻 fix it
🟢 see the green test
🚀 ship it
Writing a failing test case before you start fixing the issue is something I can really recommend, as it will ensure that:
- the bug is consistently reproducible.
- the issue stays fixed in the future, for example, when you actually refactor that code.
This presumes that you have a somewhat easy way to add a test case for the bug. If you're in the unfortunate situation that you'd have to introduce a testing framework first to actually write a test - go back to the beginning of this article. 😉
So when is the right time to refactor the horrible thing we've found? I'll try to answer this in part 2 - so stay tuned 📻
That's it for today. Feel free to reach out to me on twitter if you have any questions, or just leave a comment below. ⬇️