Motivation
The best developers I work with share this same habit:
They refactor code as they touch it
But what is refactoring? Refactoring isn't something you do at the end of every sprint. It’s also not something that you need to ask permission for. It also doesn't mean rewriting your entire application.
Refactoring is an act of improving the structure of a code without modifying its behaviors. It’s a stream of small changes. It’s a continuous practice you do whenever you touch the code. Always.
When we do refactoring, our goal is to:
simplify design
improve namings
identify and remove code smells
use design patterns for common problems
improve testability and maintainability
Writing clean code and keeping it clean is only possible by practicing continuous refactoring. Here are my top 7 tips to become a PRO in refactoring:
1. Do aggressive refactoring at small scales
When I work with developers, I notice two common mistakes related to refactoring:
They refactor at a large scale all at once
They don’t run their tests continuously
To practice aggressive refactoring at small scales, do the following:
Refactor a little piece of code
Run all your tests
Repeat 1-2. until your code is in a good shape
By refactoring a little piece of code you can make only small mistakes that are easy to correct. By running all your tests, you can ensure you didn’t change any behavior.
If you can’t run all your tests frequently because they are slow, fix that first. Your unit test suite should run within a few seconds, at max. To run your tests continuously, use continuous test runners like:
NCrunch for C#
Wallaby.js for JavaScript
PyCrunch for Python
Custom file watcher to trigger test execution
Nothing will give you more confidence during refactoring than running your tests all the time.
2. Master the refactoring hotkeys of your IDE
The best developers I work with know the refactoring hotkeys by heart. Next to increasing productivity, it makes coding fun. There is no better feeling than refactoring code with hotkeys than seeing your tests passing in the end. Passing tests are dopamine ✅✅✅
Built-in refactoring tools also make refactoring safer. Here are the most common hotkeys I use daily:
Rename
Move line
Inline variable
Extract to method
Introduce variable
Introduce parameter
Change function signature
Know these hotkeys by heart!
3. Use mutation testing before refactoring code
The biggest risk of refactoring is that we can break existing functionality. But it can only happen if you have low-quality tests. To improve tests, many people use code coverage tools, mistakenly. Code coverage is not a quality metric. It can help detect untested areas of the code, but it doesn't tell anything about the tested areas.
How can we improve the quality of our tests then? The answer is: Mutation Testing.
Before you refactor poorly tested code, you should run Mutation Testing to verify that your tests are reliable. Learn more about Mutation Testing in my previous post.
4. Turn comments into well-named components
Code comments are code smells:
❌ They are never compiled or executed
❌ They become easily outdated and rotten
❌ If used extensively, nobody reads them
But there are a few exceptions when they make sense:
✅ Explaining the why
✅ API doc generation
✅ Revealing implicit behaviors
Apart from these, don't write comments. Use intention-revealing names instead. Turning comments into well-named functions and classes is a cheap and easy win while refactoring code.
5. Follow the Rule of Three
It’s completely fine to have duplicated code. Duplication is far cheaper than wrong abstractions. But one can still improve code by removing duplication with the Rule of Three. This rule suggests that a piece of code should be extracted into a separate component only if it’s repeated three times. It strikes a balance between premature optimization and excessive duplication.
Note that you should only remove duplication if the identical pieces of code represent the same business knowledge. Otherwise you introduce risk. Here is why:
6. Don't mix refactoring with changing behavior
The are many problems with changing behavior while refactoring code such as:
Adding risk
Increasing mental load
Making code review harder
Making bug-finding difficult
Making our actions error prone
Focus on one thing at a time. Either refactoring or changing behavior. Never both. Take baby steps. To keep refactoring separate from changing behavior, use Test-Driven Development (TDD).
7. Use TDD to make refactoring a core part of development
After all, we all tend to forget to do refactoring. There is time pressure or we are just too lost in writing code. But TDD fixes this. It has a dedicated phase for refactoring. It reminds you to refactor your code in every cycle. It encourages baby steps which keeps your refactorings small and safe. Learn the other benefits of TDD from my previous post.
Conclusion
Most software engineering problems don’t come from not using the latest frameworks or not utilizing the best algorithms. Most engineering problems come from the mess we have in the code.
Refactoring is not a separate activity. It’s a fundamental part of software development. Refactor code early and often. Refactor it aggressively. Refactor it continuously. Run your tests all the time. And refactor safely with the help of TDD and mutation testing.
Never forget: the cost of refactoring a bad design is cheaper than the cost of living with it.
How Can I Help You?
There are two ways I could help you:
Transform Your Craft With TDD: Join 200+ engineers and master Test-Driven Development. You’ll not just learn TDD; you’ll master the best practices to produce quality software via real-world projects.
Promote Yourself to 20,000+ subscribers by sponsoring this newsletter.
Awesome tips Daniel!
Refactoring should be a constant activity...
Good stuff, love refactoring daily myself!