Hello, folks,
How do you distinguish between refactoring and rewriting code?
Would you usually refactor code because you have to implement a new feature or just because it smells?
What's your justification for spending time on refactorings for those who prioritize, be it your engineering manager or PM?
I have made my short notes about Refactoring vs Rewriting but I would love you to share your experience, too.
Top comments (3)
Both 'refactor' and 'rewrite' are somewhat nebulous terms when talking about code. They may mean different things to different people, and which any givne change is may even depend on what scope you're observing the change from (for example, re-implementing part of a class from scratch is usually a rewrite if you're looking at the class internals, but may be a simple refactor from the perspective of consumers of that class).
As a general rule, the definitions I personally use are:
As far as what the minimum for me to refactor is, that depends on what I'm working on in the first place.
If it's some personal project that I haven't released yet, I'll often refactor the same piece of code a dozen times before deciding on the actual implementation, testing each of the variants to see how they perform compared to each other to try and get an ideal balance of time/space utilization. Once I've picked one implementation, I'll move on to some other task in the project, but may still go back and refactor or even rewrite the whole thing if I come up with a better way to do it.
On the other end of the spectrum, if it's a published project for work, I will actively resist refactoring the code even if it stinks worse than year-old roadkill. As long as it's working correctly, I won't touch it except to add comments and documentation so that anybody else who has to work with it can understand what it's doing. The most important thing there is keeping the VCS history clean so that you can track down what change introduced a bug as easily as possible.
Austin, thank you for your reply and great points!
I feel that what you lay out here is exactly how I or many engineers I've worked with perceive refactoring. This isn't bad in any sense but is a little bit blurry.
(Also the term "rewrite" didn't seem to be the best fit to me. I just couldn't find a better word since I am not a native English speaker.)
It's like any time you rewrite the code, for whatever reason, you may refactor it along the way, whereas when you intentionally refactoring thing, you sometimes feel insecure about the changes it introduces. Either because it may be harder to bisect change history in search for bugs, or because test coverage is low or absent, or whatnot.
However, after watching Martin Fowler's wonderful talk I referred to in the article linked to this post (yes, I am urging you to read it, even though there's not much to add to what you already know), I realized that the definition and mind model of refactoring I have been using for years and always been perfectly understood by my peers, may be a of a bit wrong or inclomplete definition.
Martin Fowler defines refactoring as follows:
On top of that, it's by his definition a sequence of small changes:
I am not saying Fowler is god, even though he invented the discipline as far as I am concerned. It just feels that having small refactorings applied sequentially eases this anticipation of trouble you may have after a massive transformation introduced by something that combines both refactoring and changing code behavior. To me, after years of practicing programming alone and in teams, it for some reason feels a bit like revelation.
Extracting a method, then renaming a variable, then splitting loops, and extracting methods again seems so much more safe to me now than what I used to do.
Does this resonate with your experience somehow?
As a general rule, I do find that it is often better to refactor in small steps like this, at least while you're working on things, for three reasons:
Sometimes though, it really does make sense to just do it all at once because the intermediate states are either nonexistent or just plain meaningless. Switching the internals of a function from an iterative implementation to a recursive one is an example of something like this, there's just no sensible 'intermediate' state for such a change, so it makes no sense to try and step through one.