Stephan Schmidt
Too Many Developers Get Refactoring Wrong
What refactoring is and isn't
- There exist big misconceptions about what Refactoring is
- If you do the right thing, rewrites are minimal
- If you also do some other things, have a tech strategy and tech vision and alignment with business strategy and business vision then rewrites go away
“We have to refactor” - words dreaded by every startup CEO—or CTO. Developers want to spend significant time on “Refactoring” the code, instead of writing new features. Growth is already behind hopes and investors are unhappy. Why now?
But Refactoring is a good thing, constant refactoring keeps the code clean, flexible and readable. What is this month’s long “We have to refactor?” Most often, it’s not Refactoring at all, it’s re-architecting or re-writing.
Too many developers get Refactoring wrong, and I’m not sure why this is the case. It feels like they’ve never read the Refactoring book but got their knowledge handed to them by generations of developers before them. When the Refactoring book came out anD I’ve read it for the first time (yes I’m that old), my mind was blown. There were coding tricks in it, I had never thought about before. Sure, some parts haven’t aged well, some parts haven’t been as useful as thought, and it’s overly optimistic about object orientation, but the core is still gold, and the book should be required reading for every engineer.
Whenever a CTO tells me developers want refactoring, its not refactoring but a re-architecture or rewrite. Perhaps they got mislead by the definition “Refactoring is a controlled technique for improving the design of an existing code base.” especially by the word “design.” But Martin Fowler follows up with “Its essence is applying a series of small behavior-preserving transformations, each of which “too small to be worth doing.”'
Each of which is too small to be worth doing. There you are. But the idea of refactoring as a continuous flow of small changes to the code is lost and is replaced with sweeping changing to the architecture! We can’t do continuous refactoring, we have no time!
Let’s look at an example. The refactoring I like most is “Decompose Conditional” and says, “Extract methods from the condition, then part, and else parts.”
if user.isGoodStanding
&& len(user.orders)>5
&& user.hasOpenedEmail
&& len(user.email)>5 {
send(CouponEmail)
}
can be replaced with
if goodCustomer(user)
&& canSendTo(user) {
send(CouponEmail)
}
which is easier to read and understand. On top of that, goodCustomer
and canSendTo
might be reusable.
Last not least, the code is easier to test. While the first If
might be part of some hard to test code, at least in the second If
parts can be easily tested:
func TestGoodCustomer() {
user := User {
goodStanding: true,
orders: ...
}
assertTrue(goodCustomer(user))
}
With these tests in place, it is easier again to continuously refactor the code and keep it in good shape, which doesn’t get them in a mess and keeps a rewrite away.
You might think developers do all these refactoring, but then there is still some need to “refactor” aka re-architecture. My experience is limited, but whenever I look in a codebase that “needs refactoring” for an extended period of time, it didn’t have all the refactorings applied during development, e.G. many opportunities for “Decompose Conditional”.
This is how developers arrive at a messy code base and want a major refactoring.
But if they really need some rearchitecturing, still? How did developers arrive at a state where they feel that they need a “major refactoring” to change the architecture? They arrive at that state by creating the wrong abstractions, because they anticipated a future that never arrived. They want to remedy the situation by different abstractions again for a future they don’t know.
What to do instead: Keep abstractions in check. Be mindful and careful about abstractions. Every abstraction is an assumption about the future, and we’re not good at predicting the future (I would play the lottery if I were). So be careful with abstractions! They are the root of all evil, use them when you know where the journey is going.
Writing good code doesn’t take more time, I think, when I hear “We have no time for high-quality code” - which a lot of developers equal with deep abstractions. Writing code with ongoing refactoring doesn’t take more time either, it even makes you faster.
Writing side effect free methods as much as possible is as fast as writing bad code. Not hard-coding values are as fast as hard-coding them. Having the right size of methods isn’t slower. Thinking before you write code is faster than writing bad code and then debugging it.
For me, personally, engineering code quality as a red line, constant refactoring a must.
It’s the CTOs responsibility to help developers skill up. It’s the CTOs responsibility to create a professional engineering culture without shortcuts and bad coding. Don’t give in to pressure, keep your professional engineering up. It’s not easy for the CTO to give pushback on pressure, but it can be done—you’re not going to be fired. It’s the CTOs responsibility to draw the red line.