⏩ Introduction
Welcome back, developers friends! 👋✨
We all know that the success of software projects depends heavily on the maintainability and quality of the code. But far too frequently, refactoring is seen by developers as a one-time exercise, something to cross off their to-do list. However, we need to rethink what refactoring means in order to fully realize the potential of our code and improve our development process.
Refactoring is not only something you do when you have time or when problems pop up. It is a way of treating our code with intention and attention. It is a style of thinking. It involves fostering our codebase's evolution and adaptation as our project moves forward and continuously enhancing it. Refactoring is a chance for growth, innovation, and sustainable development rather than a burden.
By embracing this mindset shift, you can enhance code quality, improve collaboration, and create a solid foundation for long-term success.
🌟 The problem with treating refactoring as a one-time task 💪💻
A very common situation: software is developed without a continuous refactoring approach, and it grows and grows during the months. From time to time, new team members enter and face problems like hard environment setup, or fragility of the system: when you change something, something else broke up in a different place. Each couple of months, a refactoring task is in the sprint to clear up something that has become impossible to handle - and it typically takes way longer than the team thought. At a certain point, the system is perceived as so fragile that the team decides not to deploy anything on Friday. And, finally, someone says: “We have to rewrite it!”.
Sounds familiar, am I right? This is what happens when refactoring is treated as a separate task and not as part of the daily job. If you work this way, it’s simply unavoidable.
If you ask business people, they blame tech: “The platform keeps breaking, there is always something not working”.
If you ask tech people, they blame the business: “They keep asking for new features, they keep putting pressure to be fast - we should have a month without new features to refactor some stuff”.
I have to say it: I (mostly) agree with business people here!
Let me explain: first of all, I want to state clearly that putting pressure for features without a justified reason makes no sense at all - and no, “the CEO asked” is not a justified reason, “we need to catch this incredible opportunity worth 100k$” is, for example.
No excuses for that.
But, on the other hand, I strongly believe and require my employer to give me trust and responsibilities, then it’s also my duty to refuse useless/unclear pressure; and one of my others duty is to keep the code easy to change.
What does “easy to change” means? A couple of things, IMHO:
-
“easy to change” means that I’m able to develop and release small increments every single day, multiple times per day
-
“easy to change” means I can face a deadline by suggesting a smaller scope or different solution that cost less
-
“easy to change” means I can build a simple version of a feature in hours and test if it works instead of building a more complex version in weeks and then risk that no one needs it or use it
-
“easy to change” means I’m not afraid to deploy on Friday at 6 PM
As you can see “easy to change” imply not only clean code but also having self-testing code (way better if written in TDD) and Continuous Integration.
🌟 Embracing the Power of Continuous Refactoring 💪💻
We need to invest all our energy to avoid the “Friday deploy in production restriction” because it’s an anti-pattern. Ideally, a production release should cost us nothing and should be something so easy that we are not afraid of launching it on Friday at 6 PM and then leaving, or any other delicate moment.
That should be one of our main targets: deployment cost reduced to zero (or the closest possible value) in order to deploy multiple times a day, whenever we want, without any worries.
To achieve that, we must be able to refactor daily: every time we need to make a change to a piece of code that is not easy to change, we should refactor the area we need to change BEFORE doing the change.
Make the change easy, then make the easy change. [Kent Beck]
By doing this, we invest some time in cleaning up; this time is totally recovered when doing the change because it will cost less, balancing the initial investment.
But in order to be able to have this approach, there are some prerequisites that needs to be respected:
-
self-tested code: tests need to be not only under tests, but those tests must be well written so that we are sure that if the execution goes green everything is fine - with a test suite that can be trusted, we won’t be feared to make changes to improve design, and will be able to check every change we do so that we can rollback and try another one if something breaks up, without any headache
-
continuous-integration: we need to integrate code with master very often, in order to check that our changes didn’t broke up - this also embed automation to do it safely, checking for the build, test execution, syntax, etc.. - this is fundamental because when refactoring in a feature branch context, you typically run into some merge problems; while most of syntax merge issues can be solved easily from automatic GIT merge or with a quick comparison, semantics conflicts are the real problem: imagine you rename a function, but in another branch, someone add a call to that function - it will be very hard to notice it and it will likely run away and break something
Once you are ready with a trustable test suite that makes test self-testing and a CI setup, refactoring will cost very low - but you have to be careful and approach refactoring in small steps in order to maximize the chances of success!
There are basically 3 main rules that you should follow:
-
move in the smallest step possible ➡️ make the smallest refactoring change you can imagine, and avoid big changes together
-
run tests after every change ➡️ after every small step, run tests: if tests are green, commit to having a safe checkpoint; if something breaks up, either solve in seconds or rollback and try something smaller
-
use automated refactoring to speed up ➡️ Nowadays, most IDEs offer the chance to automate refactoring such as rename/extract/inline something: make a strong usage of that automation, because you can trust them that much that you can make a couple of them together, run tests (that 99% of times will be green since it’s automated so it makes mistake very rarely) and commit multiple steps of refactoring together
This mix of tools and good practices will make refactoring cost so much close to zero that there will be no excuses to refactor continuously, multiple times per task. No more excuses, fellas!
Until next time, happy coding! 🤓👩💻👨💻
Dan’s take 🙋🏻♂️
Refactoring daily is the only way to create, evolve and maintain a software system keeping technical debt under control without particular trade-offs on productivity.
In my experience, I often met a situation where refactoring was seen as a task: the most usual situation is when no one care about technical debt for months; then, someone decides that is time to do it and asks for a “feature-free” month: IT/Tech/Product team ask for a month of stop from developing new features because they want to refactor some important piece of code that has given headache lately.
Typically, business people refuse this idea, and a (figurative)fight between the two sides of the company starts - where no one wins since both decisions (attacking technical debt or keep ignoring) will bring problems.
I cannot say enough times how wrong this is. It is a very, very, very bad way of managing technical debt.
We cannot blame business people to fight the idea: we are basically asking our business to pause for weeks while we try to fix some important piece of code. Pausing a business it’s a problem, and money might be lost as a consequence, now or in the next future.
The objective of a software developer must be to never reach this situation: sometimes you simply cannot avoid it, for example, when you enter a project where they did this mistake you might end up discovering that you have no other choice at the moment - but you should see this as a one-time solution and something to avoid repeating again with all your energy and focus.
Instead of treating refactoring as a separate task to be done from time to time, we should treat it as a step in development: make a small amount of refactoring every time you change something into code.
The best approach is to refactor before doing the change because it will result in an investment that is repaid immediately since the change will cost a lot less thanks to the refactoring itself, but sometimes you don’t know enough of that piece of code to refactor before, yet it’s still a good idea to refactor a bit later once you are done (and now you know enough to understand how to make small improvements to that code).
The principle to apply is the “boy scout rule”: leave the code cleaner than you found it.
With this simple rule applied for months, we will not only see our system improve in a natural way, but we will also be sure that most of the improvements are done on the part of the system that changes most often, which typically means it’s the most important part!
As always, there are trade-offs and sometimes you might end up investing a bit more in a task with this approach, but it’s totally repaid with the unplanned work that we will avoid in the future.
Also, remember what we said earlier: daily refactoring requires self-testing code and CI and automation, and those prerequisites will make the cost of a single task basically the same with or without refactoring, but only if you refactor every time this cost will remain the same - if you don’t, on the other hand, the cost of development will increase so high that a big “refactoring task”, or even worst a rewrite of the system, will be your only choices available.
In general, as we saw with CI, single-piece flow, and TDD, all the best practices in software development are meant to avoid unplanned work, which is typically the most time-consuming and interrupting task for a software development team.
Refactoring tasks and system rewrites are very very bad for business, so it’s our responsibility as Software people to avoid those with better practices such as daily refactoring: as always, our choices are for business, not for technicalities.
Go Deeper 🔎
📚 Books
-
Refactoring: Improving the Design of Existing Code - Original Agile Manifesto signer and software development thought leader, Martin Fowler, provides a catalog of refactorings that explains why you should refactor.
-
Refactoring Workbook - William Wake outlines a proven workbook approach to learning and applying refactoring to "everyday" projects. This approach relies on examples that force the student to apply the main concepts of refactoring. As a result, readers gain knowledge of how refactoring can help improve their software.
-
Refactoring to Patterns - This book introduces the theory and practice of pattern-directed refactorings: sequences of low-level refactorings that allow designers to safely move designs to, towards, or away from pattern implementations. Using code from real-world projects, Kerievsky documents the thinking and steps underlying over two dozen pattern-based design transformations.
📩 Newsletter issues
-
Refactoring the Titanic [Deliberate Machine Learning by Laszlo Sragner]
-
4 Mistakes You Make in Code Refactoring [I Code It by Juntao Qiu]
-
Refactoring BabyAGI - Code Quality and LLMs [Deliberate Machine Learning by Laszlo Sragner]
📄 Blog posts
-
Refactoring collection from Martin Fowler's blog
-
Refactoring Guru portal
-
Refactoring Legacy Code from Modlogic blog
-
The process of refactoring Seriously Simple Podcasting from Castos blog
-
Refactoring from Scaler Topics
🎙️ Podcast episodes
-
Managing Large Refactorings (Complete Developer podcast)
-
Refactoring databases — or evolutionary database design (Thoughtworks podcast)
-
Refactoring, with Emily Bache (Making Tech Better podcast)
👨🏻🏫 Online courses
-
Refactoring Fundamentals [Pluralsight]
-
Refactoring [Upcase]