This is the second part of the article about рroject refactoring by Dmytro Khandzanov, a frontend developer on the MOJAM team. Our product is a platform for esports players and CS2 gamers, with over 4 million users across 168 countries of the world. The top priorities are interactivity, reactivity and system fault tolerance.
In the first part, in case anyone missed it, we discussed the technical aspects of refactoring, including idea generation and documentation. I recommend starting there. And if you’ve already read it — welcome, and let’s dive into the organizational nuances of implementing the developed concept.
In this article, we will go over the key milestones to achieve during the idea implementation process. We will also discuss the nuances of pitching the idea to the management, based on my personal experience and that of my colleagues. Finally, we will highlight several related tasks that inevitably run in parallel, which, although often overlooked, are just as important and deserve your attention.
Milestones
We need to start somewhere, so here’s a step-by-step plan for implementing the refactoring process:
1. Shaping the concept. The idea should be written down, with well-defined goals and a description of how to achieve them. This documentation serves as a foundation for moving forward and makes it easier to present the idea to management.
2. Consulting with the architect and tech lead. It is important that the person responsible for the project’s technical health is informed about potential refactoring opportunities. If they find the idea technically solid and give their approval, it becomes much easier to justify its necessity to management.
3. Pitching the idea to management. A refactoring effort of this scale requires significant resource, so either you secure management’s backing or you’re in for sleepless nights and weekends spent in front of your computer.
4. Idea validation and creating a technical design. Before kicking off the refactoring process, it’s important to thoroughly describe what will be done and present it to the team. This is the perfect moment for debates, exploring weaknesses, refining ideas, and testing assumptions.
5. Building a playground. This step involves implementing the core systems that will support the application within the new architecture. It’s the starting point for transitioning the existing functionality onto a new foundation.
6. Functionality migration. Once the playground is ready, the team can begin moving features to the new structure without disrupting the ongoing flow of business tasks.
Of course, a number of related processes – like team training, test writing, and documentation will also be happening alongside this process. We’ll discuss those as well.
Pitching the idea to management
Even the best intentions and most promising ideas won’t go anywhere unless management agrees to dedicate time and resources to them. And they’ll only do that if they see the proposed changes as a worthwhile investment. That’s why it’s crucial to understand the goals of the management (or business) and determine whether they align with the outcomes you’re aiming for through the refactoring process.
Since managers are not technical specialists, they rely on us – the developers — to guide decisions about improving the project’s codebase. However, it’s important to keep in mind that allocating significant resources requires solid reasoning. And while that justification is mostly technical, it should be presented in a way that makes its value clear and understandable to management as well.
This is not a guide for selling man-hours. It’s a list of arguments and approaches that will make it easier to explain the value of refactoring to management, but only if it’s truly necessary.
Arguments for selling the idea
I wasn’t exaggerating when I said most arguments are technical, but it goes beyond just that. Below, you’ll find a list of key points in favor of project refactoring, along with advice on how to present them clearly to management.
Inability to test the project
Business logic is frequently spread thinly across the entire codebase – from individual components to helper functions buried deep in various files. It’s hard to pinpoint exactly where the core business rules for a specific feature reside or whether this code is duplicated elsewhere.
This uncertainty makes it impossible to properly cover the code with tests and ensure its reliability. While you can write tests if required, your functions will likely pass those tests with limited effectiveness, which often results in more bugs appearing in the project.
Real examples of negative experience
When you pitch your ideas to a manager or project owner using only vague “all pros, no cons” arguments, you will likely get nothing more than approval and a promise that everything will improve in the future.
However, if your project experiences multiple crashes each month due to errors, and your architectural solution tackles this problem, that becomes a strong argument. It’s even more effective to present data on the company’s losses caused by these crashes, along with a simple visual explanation of how your solution will fix the problem and save money in the future.
After all, you have to admit, suggesting to invest n shekels once to avoid losing m shekels every month clearly looks like an economically smart choice in the long term.
Quantity and quality of arguments
Refactoring the whole project may seem excessive if your goal is to fix just one or two issues. If you can’t identify enough reasons to support refactoring, then you probably don’t need to refactor the project at all.
I’m not saying you should invent problems just to have more arguments. Instead, seek management’s approval for refactoring only when a significant number of issues have accumulated and refactoring becomes obvious – if not the only – solution.
Project complexity getting out of control
This overlaps somewhat with the previous ones, but I think it’s important to emphasize it on its own. It’s one of the most common issues and often difficult to convey to management. Having statistics on development timelines and bug reports from recent years can greatly support your case (so I recommend starting to track such data).
These metrics will increase inevitably; what matters is the rate at which they do so. With this data, you can clearly illustrate trends, gain your own understanding, and convey to management the potential cost of handling a task with and without refactoring over the next few years.
Trust me, managers respond much better to problems when they’re framed in monetary terms. This kind of statistics helps turn the vague idea of “increasing project complexity” into concrete figures.
Without such statistics, it becomes quite difficult to explain the problem to management, as they will have to rely on the “word” of the team lead… And if you’re working as an outsourced or outstaffed developer — well, I have bad news for you.
Meeting the team’s needs
I’ve singled this point out because it’s not obvious and might be a tough sell, but I consider it both wise and fair.
The thing is, everyone (well, almost everyone) wants ambitious and proactive people, who grow on their own and help advance the project, on their team. Yet, such people also seek projects where they can develop, work with interesting solutions and technologies, and unlock their potential. This is another reason why a project needs to evolve not only from the business perspective but also technically.
Projects are made by people, and their motivation and expertise largely determine the project’s future success. If technical development is neglected in favor of focusing solely on business features, one day you’ll end up with a team of unmotivated rowers stuck in routine solutions, while the project grows more complicated and more expensive to maintain.
Authority when presenting the idea
Refactoring the entire project or a large part of it demands considerable resources. This represents a risk that goes beyond the authority of managers at the team lead level.
That’s why it’s crucial for someone at the C-level (tech lead, architect, etc.) to review the proposal and agree on the necessity of implementation – possibly even making some adjustments. Gaining approval from a specialist at this level eliminates technical doubts for management, leaving only financial considerations as the remaining obstacle.
Supplementary documentation
Unlike technical documentation, supporting documents used for pitching your idea to management should contain only the essential information in the most concise form. Their purpose is to illustrate your ideas clearly during the presentation and simplify understanding, rather than overwhelming management with a flood of technical terms that cause confusion.
Presenting technical documentation to a manager is a surefire way to lose their interest immediately. That’s because you’ll be explaining the “how” instead of the “why.” Managers want to understand first if the work is necessary before deciding on the approach. So, don’t jump from the “selling the idea” stage straight to “selling the solution”.
Technical documentation should be the product of developing the solution, not the idea you’re trying to pitch. The business cares less about how beautifully the code is written and more about whether it gets the job done. Your job at the initial stage is to explain why the existing code is inefficient and what benefits the business will gain by rewriting it.
We are all visual thinkers who find images and graphs easier to digest than a wall of text. That’s why the best way to present complex ideas is a board divided into columns like “business problem”, “technical solution”, and “anticipated results or benefits”. The specific problems and solutions can be detailed on sticky notes attached to this board.
As for the details, they can be arranged on additional boards that management may review if they wish to gain a deeper understanding of any of the issues presented above.
Working with the team. Explaining new concepts
This topic mostly relates to Front-end development because of the wide range of approaches used in projects. It’s no secret that Front-end teams often consist of people who have completed “the best courses ever – from beginner to Senior Front-end in just two weeks” alongside specialists with formal education. As a result, coding styles can vary significantly, from messy spaghetti code in frameworks like React to some custom-written MVC pattern in an OOP style for organizing business logic.
This diversity of approaches, on one hand, lowers the entry barrier, enabling more projects to be created. On the other hand, it can complicate the understanding of more abstract concepts that experienced developers want to implement in the project. That’s why it’s crucial to explain the theoretical foundations to the team beforehand, so they can better understand these concepts before they are put into practice.
I developed a “presentation” tactic for myself. Whenever I refactor a small section of the project, I prepare slides that demonstrate how it works, how to use it, and the key guidelines. It’s important to plan a series of presentations covering various topics that need to be explained to the team and record them. This way, in the future, you can share these presentations with new team members.
Adopting conventions
“Conventions” is a fancy word for the agreements a team makes to establish a consistent code style across a project. Sure, most rules (like spacing between brackets or variable naming) can and should be enforced with ESLint, but its capabilities are limited.
As developers gain experience, they tend to form their own habits. One person might prefer using plain objects for key-value pairs, another might prefer using Map. Some prefer returning tuples, while others wrap the data in an object. There are tons of such examples.
The solution is to talk through such cases when they appear and write the outcome down in a .readme file at the root of the project. This way, there’s a shared reference for future code reviews, especially for decisions that ESLint can’t enforce. If the team can’t agree on something, the final say can be left to the code’s author, just make sure to document that too.
In our case of refactoring the project, setting conventions becomes particularly important due to the introduction of new solutions and technologies. It’s essential to agree in advance on how these technologies will be applied and to create a platform where such discussions can take place.
Implementation
Large-scale refactoring like this can take a day, a week, or even a month. Clearly, no manager will pause feature releases and bug fixes for that long, so it’s essential to plan the implementation of the refactoring in a way that allows business tasks to be handled alongside.
Setting up a playground
This is a brief stage during which a code freeze is applied. Developers build the project structure (the foundation, as you might say), which the existing functionality will later be migrated to.
That’s why it’s important to focus on the core elements. In our “abstract project,” for example, it wouldn’t make much sense to start moving code into modules right away, and the focus should be on building the middleware, as that’s the core asset of the refactoring. Creating a sample module for teammates as a reference is also a good idea.
It’s important to keep in mind that the majority of time will be dedicated to reviewing this solution by the team. The review is necessary both for familiarizing themselves with the code and for clarifying questions and implementing adjustments. Since there will be numerous changes, expect approximately 20-30% of the time to be spent on reviewing them.
Gradual project migration
After the playground is set up and deployed, the project is unlocked for adding new features. Migrating the existing functionality can be done alongside ongoing feature development. As an option, you can set a convention that around 20-30% of the task time is reserved for migrating existing functionality.
From my experience, it’s crucial at first to review teammates’ pull requests more thoroughly and be prepared to dedicate time to discussing accompanying questions.
System testing
System testing is most effective when done near the end of the refactoring, when only a small number of questionable-quality files are left to migrate. The reason is that it’s more effective to verify the business logic of specific features instead of focusing solely on individual functions or classes (though I have nothing against unit tests).
This is important because your primary goal is to ensure the correctness of the business logic through testing. This way, you can be confident that future changes won’t break anything. In my experience, it’s most effective to begin testing with controllers or module facades, as these entry points are straightforward to test and deliver the highest impact.
Conclusion
All the above, as mentioned earlier, is based on my own experience with refactoring projects. I have learned through challenges faced both in large projects with heavy legacy code and in small pet projects where architectural requirements suddenly changed.
However, the refactoring process is great because, through making mistakes, we inevitably improve projects. I sincerely hope that frontend developers will move away from the usual “quick and dirty” approaches and begin adopting strategies proven by peers in related areas like mobile and back-end development. As the applications we build evolve, becoming more complex and feature-rich, development practices must evolve accordingly.
By the way, this article has a follow-up — click the link to read another piece I’ve written – How to modernize legacy code. Technical aspects of refactoring.