Refactor or redesign?

I’ve only very recently started to differentiate refactoring from redesigning, and I’ve found it a very useful (albeit somewhat artificial) delineation to keep in mind whenever changing code.

Refactoring is the process of making small, behaviour-preserving changes to the design of your code. While refactoring can be considered a form of redesign, I’m beginning to separate both concepts along the lines of scope. When I talk about redesign I’m talking about a major change to the design, where the change has a large scope and the resulting design will bear little to no resembalance to the previous one.

So why do I feel it is beneficial to separate these two ideas? Because I’m starting to think that redesign is rarely the right thing to do.

Refactoring should probably rarely (if ever) take longer than an hour (hopefully much less – it should probably fit into a pomodoro), whereas redesign can take a full day, maybe even a week or more. Refactoring will probably touch a couple of classes. Redesign may touch a lot more, including across different levels of abstraction and along dependency chains. Refactoring will keep all tests passing and the code compiling (besides brief moments spent leaning on the compiler). Redesigns may go several minutes at a time with the local build breaking. Refactoring is generally motivated by removing a code smell such as duplicaton from code. Redesign is generally motivated by a feeling of “this is a complete mess, I’m never going to be able to work with this until it is tidied up”, or sometimes “this will make it much easier to add features in future”.

One of the points I wrote about in my last post was the importance of momentum to projects. A redesign is a big drain of momentum, as you are not actually adding features during that time. What’s worse is that you’ll tend to be redesigning for your perceived future needs, rather than as a direct response to your immediate requirements (e.g. “this is messy, so I’ll need to clean it up otherwise it will cause trouble later”). Yes, you need to clean up your technical debt, but if you can do it with small refactorings toward a better design, then you can use the feedback you get during later stories to keep the design flexible. There is a real risk of a redesign turning into a big design upfront, with all the disadvantages that entails.

On my last project I experienced the "joy" (admittedly all my own fault) of working for over a week on a redesign to allow a feature to be added more easily into the code, only to have a feature come up in the very next sprint that required us to undo a significant amount of the redesign work. Today I started to make a similar mistake, encouraging my pair to make a fairly big change to our presenters to make our feature easier to add. This required removing a whole lot of duplication, factoring out a common base class or extracting a whole lot of behaviour into strategies before we could even begin on the real work. It was only meant to take a day or so, but towards the end of the day it still wasn’t coming together nicely.

After a chat with our bearded architect, we decided to try just refactoring the part of the code that would be affected by the change. Rather than removing all the duplication, we just pulled out a base class for a small subset of the presenters and pushed and pulled a view members up and down to correct the inheritance relationship that was causing us problems. The effort took about an hour, including updating a whole lot of test code from the previous mess.

Although still not really clean, our design is now a step closer to neat, and will allow us to add the feature with relative ease (the refactoring was guided entirely by that feature’s needs, not by guesses as to what other features would also require). The next time we come across trouble with that section of code, we can take another refactoring step to remove even more duplication, but unlike my first misguided attempt we can be guided by the new requirement, so we’ll have more information on which direction to coax the design.

So from now on I’m going to be really careful to try and stick to refactoring, and resist the siren song of redesign. If there are parts of the code screaming out for redesign then I’ll start making small refactorings in the right direction, but never make large changes to jump to a new design if I can possibly avoid it. One thing I need to remember is that the design is never going to be perfect, so there is little point investing too much time on trying to get it there. If instead I concentrate on heading towards a better design by being careful to always leave the code cleaner than I found it, then I should be able to strike a good balance between cleaning the design and adding features, which should help both the overall design as well as my velocity.