There is a lot of literature about how to refactor code and what techniques to apply, but sometimes we forget one important aspect: the human factor.
In this article I would like to talk about some of the traits a team can have to promote a culture of continuous refactoring and code evolution and what are the best ways to promote it.
Introduction
Software systems rarely stay the same for long. New requirements, bug fixes, customer feedback, and changing priorities constantly change the codebase. Over time, even well-designed systems accumulate complexity.
Because of this, refactoring is not a one-time activity. It is a continuous process that helps teams keep software maintainable, understandable, and safe to change. Over time, code can become difficult to understand because it has been patched repeatedly, or because a solution has become over-engineered. It's extremely difficult, if not impossible, to get a perfect solution on the first try.
That's why refactoring plays a crucial role in the lifecycle of software.
Refactoring is usually presented as a technical skill. There are books like Refactoring To Patterns or Domain-Driven Refactoring that cover that part, but there is also an important aspect that is often overlooked: the team and company culture around refactoring.
Safety
I think this is one of the most important ones. I have worked in teams and legacy projects in which developers were too afraid to make changes to the code because of the side effects that that change could have.
People do not refactor when they are afraid of making mistakes. Creating safety is therefore the first step towards creating a culture of continuous improvement.
Let's see how we can tackle these.
No Blame Culture
For me, this is a very important characteristic of a healthy team. I have worked in teams in which if someone would make a mistake it would have been called out. For example, I worked for a company in which, if you would "break the build" (meaning pushing code that caused the CI pipeline to fail), they would send an email to the whole team with a funny picture of you.
Mistakes like making the CI pipeline fail are not that important, but sometimes we may develop new features, or fix a small bug that can potentially have a side effect in the code, and lead to a bigger bug.
That is usually when the blame starts appearing.
I personally think this is not a good thing to have, because it may discourage people from improving existing code through refactoring.
No one wants to be blamed for a mistake, especially if it was a strange side effect. That does not mean that we just move on and forget about it, it means that we may need to understand our domain better to catch those possible side effects, or to have better mechanisms to detect them.
We will see how we can achieve that later in this article.
Beyonce's Rule
If you liked it, then you shoulda put a test on it.
I really like this rule, and I use it every time. If something breaks, or a strange side effect appears that it was not detected, I mention this rule.
If a feature is considered important, the team should expect to invest in protecting it through tests, documentation, or other validation mechanisms.
Testing
This is a very important topic. We discussed Beyonce's Rule earlier, but it is also important to have good tests. Otherwise, you end up maintaining not only the production code, but also the testing code, meaning more code and more maintenance.
Code Coverage Metric
Code should have minimum {x}% code coverage
I am personally not a big fan of this metric, because it may result in tests that are there just to fulfil this metric.
Let me give you an example, using Go code:
type User struct {
Name, Surname string
}
func NewUser(name, surname string) User {
return User {
Name: name,
Surname: surname
}
}
The function NewUser probably does not need a dedicated test, since there is no behaviour at all, and having the need to test it (because of the metric percentage) can have two consequences:
- Worst scenario: test it, adding tests that have no value.
- Best scenario: the developer understands that there is no value on the test, and the reason is that the function itself is not needed, triggering a refactor on the code to remove that function.
This is just a very simple case, but the goal is that testing code, for the sake of testing code is not a good idea. The goal of having tests is that you feel confident that they are going to catch bugs in case of new development or refactoring.
Test Behaviour, Not Code
In the previous example we saw that some functions do not deserve dedicated tests. This is a controversial statement because you may have heard that you need to test all your code.
The whole point of testing your program is to prove that your software provides the functionality that you expect, not that the code "interactions" are the ones that you expect.
That's why I prefer testing behaviour whenever possible instead of relying heavily on mocks, because using mocks in a test often validates interactions in the code, not the expected behaviour.
Whenever possible, test that the initial and final state of the system are correct rather than testing the implementation details used to reach that state.
Steps To Refactor
In some cases the team may not know where to start refactoring, there are several rules you can use to refactor your code, one of the most famous one is the Boy Scout Rule, but another approach is to identify areas of improvement in your code, and plot them inside a Effort-Impact Matrix.
One of the first areas of focus when building a refactoring culture is to identify the Low Effort-High Impact tasks, and prioritise them.
AI models can play a role here by helping identify those tasks, or even helping implement them.
Early wins help demonstrate the value of refactoring and build trust within the team. Once that trust exists, larger initiatives become easier to justify and prioritise.
The next natural step is to tackle the High Effort-High Impact tasks. It can also be used to align the developers in a common goal, by subdividing these kinds of High Effort-High Impact initiatives into smaller independent tasks.
Prerequisites
One of the key factors for a successful refactor is having good test coverage, so the first step is to make sure that the tests cover the behaviour that you want to refactor, and that they test behaviour, not code, if not:
- Refactor the tests to test the behaviour and not the code.
- Add more tests until you feel confident that the expected behaviour is covered.
Another key factor is remembering that code is read far more often than it is written, meaning that readability should be a priority when rewriting code.
Different teams have different workflows, but most of them end up creating a pull request that needs to be reviewed by their teammates.
Pull Request (PR) Comments
One thing I have noticed is that some developers see comments on their pull requests as a sign of "failure". It's important to be clear that having comments in a PR is not a failure at all.
It's normal to have comments in a PR since people can have different points of view on how a feature/refactor could be implemented. Receiving the same comments repeatedly across pull requests is something the team should address.
As a team, you could consider developing your own tooling to avoid those repetitive comments, for example, in Go you can create custom analyzers that could tackle those possible repetitive comments.
Refactoring Must Be Rewarded
Teams do more of what is recognised.
If developers are only rewarded for delivering new features, refactoring will always be seen as secondary work. On the other hand, if improving maintainability is recognised as valuable work, developers will naturally invest more time in it.
As a technical lead, one of the simplest things you can do is publicly acknowledge improvements that make the codebase easier to understand, safer to modify, or easier to maintain. For example, I always encourage team members to demo not only new features, but also improvements that make the code easier to understand and maintain.
Final Comment
A culture of continuous refactoring is not built by process alone. It is built by creating an environment where developers feel safe to improve code, have the tools to validate their changes, and are recognised for investing in the long-term health of the system.

Top comments (0)