Illusion of refactoring
Refactoring is not rewriting and is not about changing things because I don't like them. It needs to be more important than that to refactor.
Refactoring is a disciplined technique for restructuring an existing body of code, altering its internal structure without changing its external behavior.https://refactoring.com/
We usually call refactoring the rewriting, this is completely a misunderstanding of what is refactoring. You don’t refactor anything if you start from scratch trying to copy features of the old system (rewriting).
Rewriting
It’s important to understand when to refactor and when to rewrite.
Rewrite could be the consequence of:
Software that lost the ability to change easily
Software using old technologies just known by small group of developers, hiring reasons.
Software lost the ability to change easily because of one of the intrinsic problems of software, accidental complexity.
Accidental complexity relates to problems that engineers create and can fix. For example, modern programming languages have abstracted away the details of writing and optimizing assembly language source code and eliminated the delays caused by batch processing, though other sources of accidental complexity remain. Essential complexity is caused by the problem to be solved, and nothing can remove it; if users want a program to do 30 different things, then those 30 things are essential and the program must do those 30 different things.
Accidental complexity is not the same as Tech Debt. Tech debt is about decisions of your codebase and the delta between those decisions and your knowledge of the problem right not.
Technical debt is a subset of problems within accidental complexity, but they are not the same. For example, having a process to merge a feature in main that requires two approvals of two different people apart from the main developer is accidental complexity, but it’s unrelated to tech debt.
Tech debt is about your design, much more related to the decisions done in the past modelling your code to resolve the problem, the essential complexity.
Tech Debt is often a choice (trading speed today for quality tomorrow), whereas Accidental Complexity can also arise from sheer ignorance or bad tooling. It’s a subtle distinction, but a helpful one when talking to management about why the debt exists.
As you write code, and you have results, you obtain feedback that makes you to learn and understand the problem differently. The delta between what you know now vs what is in your codebase is Tech Debt.
Tech debt is accidental complexity, but it’s unrelated to for example too much bureaucracy to merge code in your main branch, that’s accidental complexity but not tech debt.
Our codebase as time elapses increases their accidental complexity, it’s a natural process. A simple metaphor is the second law of thermodynamics.
The Second Law of Thermodynamics states that the total entropy (disorder) of an isolated system always increases over time, meaning heat naturally flows from hotter to colder objects, and it's impossible to convert all heat into work in a cycle. This law explains why processes are irreversible, like a broken egg never reassembling itself, and sets fundamental limits on energy conversion efficiency, such as why heat engines can't reach 100% efficiency, with some energy always being lost as unusable heat (increasing entropy).
The only way to maintain heat in a system is to introduce energy.
But energy is expensive, it means to pay more.
In the case of software, very complicated codebases to change means more time of developers fighting against Accidental Complexity, Tech Debt, bugs.
All of that reflects in unmet budgets, dissatisfied customers, money lost.
To reduce the cost of introducing new features into the codebase, we can refactor. Refactoring is the way to reduce accidental complexity, to pay tech debt, to avoid bugs related to the cognitive load of the application.
The intention of refactoring is always to reduce accidental complexity because we will need to maintain that codebase for years, whereas updating the code is cheaper than rewriting it completely over and over again.
But then you could say, I don’t need to refactor if I do the software the first time right. This is fair, but what will happen if you do the system the first time right?
Let’s say that after 1 year of a real success business, you realize you have a competitor doing the same as you with new features. It’s affecting your business, so you decide to copy those features. If any of those features go against any assumption in the old system, our friend Tech Debt will visit you.
If you are able to build a system the first time right, you didn’t learn anything in the process of building it. You knew all the things required upfront. Learning is a subproduct of the building process. If you don’t learn anything you are just copying things you did in the past, why do you need to copy things. Is it not better to adapt the previous codebase? → refactor
Naive Refactoring
As we said before refactoring has the intention to reduce the cost of adding new features, simplifying our codebase.
Refactoring is not about preferences for coding, I don’t care if you prefer to use a loop to calculate anything instead of using streams. We as developers need to allow our team to agree on what is good and what is bad, but that’s not so important.
If the majority of refactors in your codebase are done based on preferences, your team is in the illusion of refactoring, completely useless.
You need to refactor if you for example has two ways to implement the same behavior in different scenarios, that are basically the same thing, and you need to introduce a third one.
Then at that moment watching the code, you have realized (learned) that you could add that new feature easily if instead of two implementations you had one.
At that moment you discover tech debt that was in your system, then just pay it because it is always cheaper to pay tech debt when you discover it, refactor that part and after that introduce the new feature.
To refactor you need a proof that there is a simple cheaper way of doing things, don’t refactor just because you don’t like the code.
I like JB Rainsberger rules of when to refactor (refactoring just enough):
refactor only when you’re about to touch that part of the code
refactor only enough to support what you need to do now (Kent Beck’s “make the change easy, then make the easy change”)
leave SMELL/REFACTOR comments any time you notice a smell or refactoring and don’t intend to address it now, due to some other rule
Clean Up Before Moving On, with preferably a very specific list of things to clean up or a time limit otherwise
But don’t understand me wrong, you don’t need to plan refactors at long term, don’t refactor in big batches, but refactor mercilessly in small increments.
Refactoring is part of your day to day, every day we should try to refactor in the code we are working on.
The best teams I’ve worked on were all applying this refactoring mercilessly idea.
How to refactor?
Refactor in small steps, a refactor that lasts days to be merged is usually a rabbit hole.
Refactor just when you are in a green state, when all the tests passes.
If you are in this scenario, try this:
Set up a goal of what you want to achieve. To reduce accidental complexity or to pay tech debt.
Try to understand how the current code works, and their problems, use the hacker mode to learn.
Use the smells in the code to list the refactors to apply to achieve your goal, you don’t need to have all the steps in your plan to start with just one or two steps is enough.
Order those steps using the simple possible refactor, that help you
Rename
Extract Variable, Constant
Inline
Extract Method
Move Method
Extract class
Refactoring is like brushing your teeth, not brushing them will not have consequences in the next week but in 20 years you will lose your teeth. The same happens with refactoring, in a pair of years your codebase will be a nightmare if you don’t refactor.
But please don’t fake refactoring, solve real problems that affect your codebase, try to avoid the illusion of refactoring.


