From time to time we developers like to engage in a bit of code cleaning. Well applied refactoring can do wonders for a code base, but we need to make sure we are doing refactoring for the right reasons, and in a way that is actually beneficial.
"Duh. Of course!" you might think. But it's not hard to set out refactoring motivated by the wrong things entirely. I know I've done it.
Anyway, I've been doing some refactoring this week, and it got me thinking about the reasons not to refactor and reasons to refactor. This is in no way scientific, just some thoughts I've had regarding what to refactor and what to leave alone.
Some reasons not to refactor
Lack of understanding
When we come across code we don't understand it's easy to be tempted to rip everything out and start again, replacing existing code with new code that we understand (because we've written it).
But before we do so, let's take a minute and consider the idea that the previous person working on the code isn't a complete idiot.
If our lack of understanding stems from unreadable code and the conditions are right for refactoring, then we should refactor.
But often the problem is with us. Maybe we haven't spend the time required to fully understand the domain, maybe we haven't even spend sufficient time trying to grasp how the code is structured. If that is the case, we should make an effort to understand what is already there, before we start to refactor. Chances are, when we do make changes, the result will turn out a whole lot better.
If we want to change a vital piece of code, and we don't have the tests necessary to sufficiently verify that we are not breaking the intention of the code, we should not refactor.
Not now at least. What we should do instead is to write the tests we need, then refactor. Writing the tests will also help us ensure that we have a good understanding of the code we are about to write.
Sometimes we want to refactor simply because the code we are working on is very difficult to test. In those cases we should aim to write tests and change the code in lockstep. Changing small parts at a time, with focus on making the code sufficiently testable first.
Refactoring is best delivered in small increments but once in a while we might be tempted to make a big change. Sometimes that is the right thing to do, but we need to be careful.
Let's say our code relies heavily on a component, and it has become outdated. Furthermore let's say changing that component out for a newer more modern alternative is bound to give us a performance boost, and it will solve some major headaches we have with the current component.
But upgrading is a big job, and we don't have the time to do it. But we could do it in some places, the places that really matter to us right now. We've got time for that.
So that's exactly what we do, and now we have two components, that are responsible for doing the same type of job but behave differently. Keep doing that and soon we'll have a disorganized mess of overlapping components, and no standardized way to deal with anything.
Sure it's worthwhile to update and upgrade, but we need to make sure we can follow through.
New and shiny
We do this all the time. We change something, not because it doesn't work, or because we want to improve it. But because something else is newer and more shiny. Because it has the appearance of being better.
"All the cool kids are doing it this way" isn't in itself sufficiently good reason to change well-written and perfectly understandable code. There has to be additional benefits to making the change. Our work week has a finite amount of hours, let's put them towards creating real value, not just keeping ourselves busy.
Some reasons to refactor
So when should we refactor, given that the conditions for doing so are right?
There can be many reasons why a code base is hard to work with. Maybe the people who wrote it had such a good understanding of the domain that they took shortcuts, making the code incomprehensible for anyone less experienced.
Or maybe the code is just unreadable, because of carelessness, skill level or something else entirely.
Good naming, flow and separation of concerns will help make code accessible. Accessible code is satisfying and efficient to work with. The opposite however is toxic. If we are spending a lot of time reading and re-reading code in an attempt just to understand it, and we have to repeat that process every time we return to it, it's probably time to refactor.
Aligning with reality
The world changes, and businesses change. Code has to be continuously evolved to reflect reality. When the concepts in our code go stale and become misaligned with the business it is intended to support our understanding of the code corrodes and bringing new people onboard becomes a lot harder.
There is technical debt, and there is technical debt. The thing about debt is that we always pay interest. Sometimes the interest is low, and sometimes it's high, it depends on the debt.
When our technical debt is in a isolated component that isn't subject to a lot of change, and that nothing else relies on, we pay low interest, and maybe refactoring isn't worth the price.
But what if the our technical debt is somewhere that services other code, and we keep evolving that other code. Then every time we add something new, we create more risk and problems that eventually need to be sorted. We pay more interest on our debt.
When the interest is getting steep, it's a good sign we should consider refactoring.
There is no conclusion here. Whether or not to refactor, and how much to change is best decided on a per case basis. It's very too easy to just start changing things. We owe it to ourselves and our code to consider our motives before we go to work.