Let’s face it: technical debt is inevitable and rewriting your code every 6 months is not an option. Refactoring is a complex topic that doesn't have a one-size-fits-all solution. Frontend applications are particularly sensitive because of frequent requirements and user flows changes. New abstractions, updated patterns and cleaning up those old functions - it all sounds great on paper, but it often fails in practice: todos accumulate, tickets end up rotting in the backlog and legacy code crops up in every corner of your codebase. So a process of continuous refactoring is the only weapon you have against tech debt. In the past three years, I’ve been exploring different strategies and processes for refactoring code. In this talk I will describe the key components of a framework for tackling refactoring and I will share some of the learnings accumulated along the way. Hopefully, this will help you in your quest of improving the code quality of your codebases.
Fighting Technical Debt With Continuous Refactoring
AI Generated Video Summary
This Talk discusses the importance of refactoring in software development and engineering. It introduces a framework called the three pillars of refactoring: practices, inventory, and process. The Talk emphasizes the need for clear practices, understanding of technical debt, and a well-defined process for successful refactoring. It also highlights the importance of visibility, reward, and resilience in the refactoring process. The Talk concludes by discussing the role of ownership, management, and prioritization in managing technical debt and refactoring efforts.
1. Introduction and Background
Hello, everyone. It's such a nice view to be in front of a stage after such a long time. I've been here before. I had a great time. So as Yanni said, my name is Alex. I work at code sandbox. I'm organizing JS Heroes. Our next event is May 2023. I want to talk today about refactoring. But before I start, there's this link at the bottom. Bit.ly slash Alex refactoring. You can find the slides there. You can also find me on Twitter at Alex and Moldovan. That's pretty much about it.
Hello, everyone. It's such a nice view to be in front of a stage after such a long time. You have to excuse my voice, the Berlin weather was not kind to it this week, unfortunately. But it's such a great city. I've been here before. I've been here at this conference in 2018. I had a great time.
Oh, yeah. So let's start. So as Yanni said, my name is Alex. I'm coming from Romania. I work at code sandbox. Anyone here use code sandbox? Nice. Alex. Nice audience. Cool. I also, as mentioned, I'm organizing JS Heroes. This is a community event based in Cluj, Romania. Our next event is May 2023. So I'm looking forward maybe to seeing some of you there as well. Come find me after the talk. We can talk more if you're interested in this.
So I want to talk today about refactoring. But before I start, there's this link here at the bottom. Bit.ly slash Alex refactoring. You can find the slides there if you want to follow along or if you want to find them later. They're already online. And there will be also after the talk. You can also find me on Twitter at Alex and Moldovan, while the platform hopefully still is running. So yeah, that's pretty much about it.
2. Why I want to talk about refactoring
I want to talk about refactoring culture. I'm fascinated by why we haven't figured out how to refactor our code without impacting product development. We can build engineering teams and cultures around introducing refactoring as any other task. Understanding that it's okay to live with technical debt, we need to manage it. I will present a framework called the three pillars of refactoring: practices, inventory, and process.
Why I want to talk about refactoring or why is this talk about refactoring. I want to talk about refactoring culture. I'm not here to tell you ways of refactoring code or techniques to improve your code or improve your React components or frontend in general. I'm mostly fascinated by why we haven't figured out how to refactor our code, our code bases without really impacting product development.
I worked with so many different teams over the past years and especially with product teams. There's always been this problem of, okay, we accumulated technical debt, now it's time to do refactoring. So, you know, project managers, please step aside. It's time for the engineers to take the stage and they'll be working for one month rewriting everything, introducing a new framework or whatever just to solve this technical debt. I think we can do better. I think we can build our engineering teams and our engineering cultures around introducing refactoring and treating it as any other task on a project.
And this became even clearer to me a few months ago when we introduced this new thing in the code sandbox code base. So, we have this thing called a pitcher provider. Pitcher is our engine for running the editor. So, when you run the code sandbox editor, you have this pitcher thing that serves you all the data from the VM. And we introduced a new way of consuming pitcher data. So now what we have is a legacy pitcher provider and a pitcher provider, right? And that's perfectly fine. We never said, okay, now we have to stop everything at code sandbox and we have to focus on getting rid of the old way because we have a new way of consuming data. And I think this is really valuable. Understanding that it's okay to live with technical depth. You don't have to feel it necessarily as a burden. But you do have to manage it. So, actually this happened while I was preparing for this talk. And while I was also doing that, I realized that maybe the title is wrong. Maybe it shouldn't even be fighting technical depth but rather managing technical depth. So, what I'm going to show you in the next couple of minutes is a framework for how I think we can manage technical depth in engineering teams. And I'm calling it the three pillars of refactoring. Because, well, obviously it has three pillars. One is practices, one is inventory, and one is the process. And I'm gonna take them one by one and explain what I mean by them. So, first off, we have practices.
3. Goal, Practices, and Inventory
Your goal is to reach a certain architecture and have a way of composing components and consuming a design system. Establish coding practices and guidelines to structure code and document your team's way of working. Inventory is often overlooked but crucial. It involves gathering facts, logging technical debt, and prioritizing solutions in a separate document called technical debt accounting.
This is your goal. So to say, from the engineering perspective. You want to reach a certain architecture. You want to have a certain way of composing your components. You want to have a way of consuming a design system, let's say.
So you have here all your patterns and your architecture. You can draw your diagrams, you document how the engineering team works, how they think like very high-level about where they want the codebase to go. But you also then drill down, you get lower and lower in terms of abstraction.
So you can have in your practices, how do you normally structure code? Do you create folders for each component? Do you split files by function? Do you split files by feature? How do you just structure your code in general? And even lower at the code level, you can have your coding guidelines. And I'm not necessarily thinking here about things that can be automated, but just patterns that you and your team figure out, hey, there are three ways, and just as Nick showed, there are three ways of doing something. Especially with React. Let's pick one. Let's have our way of working documented. We use Code Sandbox, these kind of general coding guidelines that we have internally and we reference in all the PRs.
So whenever there's a discussion in a PR, like should you compose components like this or like that? Oh, yeah, actually, there is a guideline for that. If there isn't a guideline for that, now is a good time to just, you know, add it there. So these are practices, right? This is the... Like this is kind of giving you the North Star of what you want to achieve with your refactoring process. And now we move on to inventory, which may be the most important one and may be the most overlooked.
And in my past experiences with different teams, I noticed that a lot of people treat this rather superficially. So inventory is about gathering all the facts. What happens in the codebase? Now how far are we from the desired result, right? So this is about logging things, let's say, in the backlog. That's one thing that probably is the most common thing whenever people have a technical that they know they want to refactor. They'll probably create a ticket.
What we noticed with backlog tickets at code sandbox is that they tend to just rot there in the backlog because no one checks them out. So we started a new thing called the technical debt accounting, which is a separate document that we reference, again, in PRs. Whenever a PR, let's say, introduces a technical debt because of different reasons, like no lack of time or we just simply don't yet have the abstraction for something, we say, OK, let's put this in the technical debt accounting document. Let's explain it there. Why did we introduce the technical debt? What's a possible solution for it? Who owns it? Who is not necessarily who will solve it, but rather who will raise the flag that, hey, you know, this is important and we haven't solved it yet? And then, finally, we assign a priority to it. And this is also part of the inventory process.
4. Assigning Priority and Planning
Not all technical debt is equal. A funny example of this is a component we built more than a year ago. It's a markdown renderer for code sandbox. We never bothered to refactor it because it works and hasn't been touched. Once you have your priority straight, start planning for the important technical debt using documentation like RFCs. Stick with the plan but don't stop other processes.
Assigning priority. Because not all technical debt is equal in a way. And I have actually a funny example of this. So more than a year ago we built this component on a Friday. Me and a colleague. We wanted to build this markdown renderer for code sandbox. So whenever you open a markdown file, instead of seeing the markdown code, you can also see the preview how that markdown looks like.
So we took three or four hours or so, we put there a bunch of dependencies, libraries that do all the thing. We just hooked up stuff. We added some plug-ins. So it has some custom functionality. Like, you know, can follow through paths, open files and stuff like that. But we never bothered to refactor this. Why? Because this file has not been touched ever since. It just works, right? So if it works, why would you need to touch it? Because this file is kind of like lives on the extremity of the codebase. It's not something that developers open daily and have to struggle with it and go through it and understand what's happening there. You just take it as a black box. Oh, yeah, that's one component. It works. If and when this will become a problem, we will start giving it a higher priority in terms of technical depth. But until then, probably we have other, more important things to tackle.
And finally, once you have your priority straight up, you can start planning a bit. Also, part of the inventory step. It's very important to say, okay, and especially for the more complex things. I'm not saying here you should plan for when you want to rename something or when you want to clean up some code. But rather, for the more important technical depths that are really tricky and maybe are multi month span over time, you can have something like RFCs or just any kind of documentation that allows you to plan ahead and allows you to share with everyone so that everyone has a common understanding that, hey, we have this technical depth, here's the plan of how we are going to solve it. It's going to take whatever. Three months, six months. It doesn't really matter. We're going to try to stick with it, but we're not going to stop any other process.
5. Refactoring Framework and Rules
To successfully refactor, you need to have clear practices, an understanding of your technical debt, and a well-defined process. These three pillars form a framework for effective refactoring. Without all three pillars, refactoring becomes challenging and less methodical. In the second part of the talk, I will provide examples of rules that the engineering team at code sandbox follows to make refactoring successful. The first rule is to make the refactoring process visible.
This is not going to block us because we have the plan to kind of mitigate this depth. So to recap these first two pillars that we talked about, it's almost like I said, practices are your goal. So you say, okay, this is where we want to go eventually. And then with the inventory, you kind of say, oh yeah, we are around here now. We're this far away from this good idea, good solution or good architecture that we want to get to. And I'm not saying perfect because you shouldn't necessarily aim for perfect here. And then inventory will give you this kind of gap, will show you, oh yeah, you're this far away from your goal. So now it's time to apply the process and go and refactor things and just take the time, take the plan ahead and handle all these changes.
So if you do these two things before, if you have your practices clear, if you have your inventory, if you know what the technical depth is, then process pretty much is the same thing as any other task, right? The only difference is that this time it's the engineers that kind of give you this analysis before, right? The same way as project managers will do, or business analysts will do the product analysis upfront and will say, okay, we have to deliver this feature, it's going to have this impact or it's going to work for these users in this way, the same way you have your process for those tasks and the same way for refactoring. If you do those things upfront, then you just do pretty much everything that you do on a regular task. You have the execution, you have some ownership, someone gets assigned to some tasks, it takes some time, it has some estimates, you have some progress, you have a definition of don, when is this task going to be done with. So here's kind of the overview of everything, just as a quick recap, while I maybe take a sip of water here. I found this also very important to try to visualize them as they are. And also, maybe one thing I missed here, it's very hard to do refactoring in a methodical way if you don't have all the three pillars. That's why I'm calling it a framework for building this thing. Think about it. If you don't have the practices in place, what are you doing? You most likely have engineers just refactoring because they like certain patterns, but it's not the common ground that the team aims for. It's not something you settled on. If you don't have the inventory, you can have the practices. You know you want to get there, but you have no idea what's your technical debt. what are you actually solving or maybe refactoring in vain. Of course, you can't do it without the actual process, which is doing the actual changes that you want to do. Right. We've been through this.
Now, in the second part of the talk, I want to give you a couple of examples of things that we do right now with the engineering team at code sandbox. I'm calling them rules to make this work. Because I'm sure that you're thinking okay, this is very theoretical and maybe doesn't apply to my project or my context. But I think with a couple of examples of just things that teams can do, I think it will become a bit clearer. And I'm calling them rules to make it work, because it kind of feels like you need certain rules to guide you through the whole process. And the first one is to make it visible.
6. Visibility, Reward, and Resilience
One big problem with refactoring is the misconception that it should be done in the dark. In a methodical team, visibility is crucial. Have separate tickets for refactoring in your project management tool. Make separate PRs for refactoring. The second rule is to make refactoring rewarding and celebrated. Celebrate code removal and involve the whole team. Get rid of code that doesn't spark joy. Make the refactoring process resilient.
One big problem that I have with refactoring in general is that there's this preconception that refactoring should be done in the dark. Engineers just slip some commits there at the end of a PR just to improve some code. And that used to be like a good practice. But I really think that in a more methodical team where you really want to stick to a plan, you need to make all these things visible. So yeah.
Have clearly separated tickets in your project management tool that you say, I want... During this cycle, during this sprint, whatever process you're using, we are going to handle these things which are either technical debt refactoring or whatever. Make separate PRs for refactoring also. Like, if you put changes on your product PRs that are refactoring the code, first of all you're making it slower to integrate new features because now the code review may be twice as more complicated because you have more changes in the PR. But also, if you think about it, you should be looking differently at a PR that handles product changes versus a PR that handles refactoring. It should be a different objective and the level of the review should be slightly different.
The second rule is make it rewarding for the team. You need to have the buy-in of everyone in the engineering team. Which means you need to make sure it's easy to contribute to any refactoring effort. It's also rewarding. It's also celebrated. Refactoring can be quite daunting, because most of the time it's not really the most glorious work that you have to do. So what we do is we celebrate. Whenever we remove code, we say, yes, we did this. And everyone celebrates in the team. pretty much the same feeling as if you just shipped an important feature, because you shipped something on your road map, you shipped something from this refactoring road map that you have in parallel to the product road map. And also one thing which we recently started is getting rid of code that doesn't spark joy. And we have this thing now, every now and then, we call the Marie Kondo of the code base, where we meet up with the engineering team on a Friday in our virtual office and we start taking apart stuff from the code, things that we don't need or just things that in general can be rewritten. We take the time and we really celebrate this. We even have it now very formal that we gather all these ideas on a monthly basis and we say, oh, for next month, we're going to take care of these things. Maybe things pop up during the month and say, yeah, let's just do it during the Marie Kondo Friday. So it just became a thing since a couple of months ago. Last thing is make it resilient. So not only do you need it to make it visible, you need to make it rewarding, but you need to be aware that this at the end of the day is not product delivery. Product delivery will most likely get a higher priority and will most likely and likely tends to get other things out of the way, right? So when that happens, when you hit a tight deadline and everything, you need to make sure that the process is resilient.
7. Ownership and Refactoring Culture
Even if it's on low priority, even if it's on low effort, it's still there, right? We have weekly engineering huddles with the entire team to discuss the health of the code and raise any technical debt concerns. Assigning owners to these efforts is crucial, even during busy periods. Ideally, someone in the team should take ownership of the bigger refactoring process. Every team has a person that drives the refactoring culture, and if you don't know who that is, it is probably you. Thank you very much, and happy refactoring.
Even if it's on low priority, even if it's on low effort, it's still there, right? You don't lose track of it. You don't let technical depth overwhelm the whole thing.
What we do is we have these weekly engineering huddles with the entire team. It doesn't matter if you're part of any of the product teams inside the company. You come to this meeting and there's no discussion on delivery. There's no discussion on releases. We just talk about the health of the code. We just talk about anything DX related that we raise the flags, right? If we have technical depth that is not addressed and is on high priority in our document and things like that. We don't only talk about it. Of course, we also take notes, right? We assign owners to things. Someone has to take ownership. Someone has to carry the flag. Even if we're going through this rough period where we need to deliver on a rolling basis, keep track of these efforts. Even if it's just in the back of our minds.
And like the last question, if you have people just owning different parts of the refactoring process, who owns the bigger process? And I think that someone in the team ideally should take that role. And I hope that this talk or some of the ideas that I shared here will kind of encourage you to be that person in your team. And I adapted this quote. But I say that every team that has a person that drives the refactoring culture... Sorry, every team has a person. And if you don't know who that is, it is probably you.
Thank you very much, and happy refactoring. Thank you so much. Thank you so much, Alex. Would you take a seat? Such an important, important talk. I personally find myself currently under a lot of technical debt, but my only solace is that I wrote the technical debt so I can only hate myself. All right, so I think the laughter means that the questions are probably up, and there's one really good question that I think we need to start to. So you showed the PR where you removed, you know, thousands of lines of code. How do you explain the value of this to Elon Musk? And maybe more specifically, I think the question is... So management has a different view of debt than engineering. For engineering and normal, everyday people, debt is bad.
8. Managing Technical Debt with Management
Debt is leverage for management, but it's important to have a conversation with senior management about the consequences of accumulating technical debt. Showing metrics of productivity can help them understand the impact on the team's speed. Pick one specific issue and demonstrate how it affects productivity. Making a big diff may please people like Elon Musk who want to see a lot of code written.
But for management, debt is leverage. It helps you move faster, it helps you get things done. So how do you have this conversation with your senior management if you do need to, where your engineering team has maybe slightly different conception of it than your management does? Yeah. I think this is a tricky one because I'm personally... I feel like I'm in a privileged position because our founder is technical, so he understands those things, and I'm sure there are teams in which it's a bit harder to do that. I don't know if just throwing metrics works in this case, but in general explaining... I guess people that don't really get the consequences of accumulating technical debt can probably understand if you just show some metrics of productivity, right? The team is slower because they have to just pick up one piece of code that bugs everyone, right? Those are the kind of things that, for example, we tried to solve with the Marie Kondo efforts, right? Oh, there's this annoying router component that we have to go daily and it has some like random imports and we have to solve it. Or there's this very weird layout component that kind of renders all the subparts of the application and it always bugs us because we can never find something in there. So, like, I would pick one thing and just show how bad productivity is. Just show, even like show a recording of someone doing a small change and how hard it is to find what they need to change. Yeah, that's a very good point. And also, I think Elon Musk will like when you make a big diff because he just wants to see a lot of code written. Yeah. You know. Yeah, exactly. Like how would he understand the code anyway? So. Exactly. Feed them shit and keep them in the dark, isn't that? You know, how we do. Yeah, exactly. Right.
9. Prioritizing Tech Debt and Refactoring
There's no specific number for how much time should be allocated to refactoring in an engineering team. It varies from team to team. However, it's beneficial if everyone in the team is involved in refactoring. This distributes the workload and ensures that everyone is contributing to both refactoring and product development. Having a process in place, such as documenting and creating tickets, can make it easier for the team to prioritize and contribute to refactoring efforts.
All right. There's a quite a few questions about prioritisation. And I think you addressed it towards the end. But I think the one thing that everybody kind of wants to really know is kind of rule of thumb is how do you prioritise sort of tech devs and how much time would you allocate on it? Like in an engineering team. And I understand this is going to be different teams and different life cycles of the product. But like, is there a number that you could, you know, anybody could take home to their bosses and go Alex Moldovan from Code Sandbox said that we should spend at least 70 percent of our time refactoring. Yeah. I wouldn't want to, I wouldn't want people to sue me later for giving them false numbers. I think it differs from from team to team. And it also what I found maybe it's, I'm just avoiding an answer here. But what I found useful is that if everyone in the team is doing it, it doesn't feel like anyone is doing it in a way. You know, like it's just distributed. So like, it's not like, oh, one person in our team of six is doing refactoring daily and the other five are doing just product development. It's more like every one of the six engineers are doing work. And they're delivering also on product stuff. So you don't really, you don't really, it's kind of like, it puts the team in a more like a favorite position. Because you don't, you no longer have to think in like one person is out of the equation because they are doing the heavy work of refactor. But you do need to have things up front. Like, it's, I understand it takes time to, for example, document things and it takes time to even like create a ticket, let's say. So if you have the process in place, it's much better. If you have all the, all the things already up front, then just flagging another tech dev that was included or just any anything else becomes more, more easier. Like the entry barrier is lower to contributing to the effort. For sure.
Refactoring with PRs and Test Coverage
I suggest showing this talk video to your bosses. Refactoring with PRs can be hard, especially in complex delivery processes. One approach is to keep refactoring PRs alive for longer and merge them after important releases or QA. Long-living branches can help manage refactoring work. Test coverage is crucial for safe refactoring, providing certainty and allowing changes to propagate. A variety of tests, including unit tests, are important for refactoring.
And then even though we didn't get a number from Alex, I do, you know, suggest that you do show this talk video to your bosses. I think it is going to be at least a great conversation starter.
We have a couple of more questions. I think that I know that you avoided sort of technical topics here, but the most popular question is a little bit technical, if I may. Yeah. So refactoring with PRs is hard. Thoughts on trunk-based development, feature flagging, etc. Are there alternatives other than your standard GitHub PR flow that you can, you know, do for to make refactoring easier? You can think now of anything in particular. I think probably it does get more complicated once you have a more complicated delivery process. So if you need to ship on a weekly basis or every two weeks, or you're working towards like an RC branch, and then you have to figure out, okay, when am I going to include this refactoring work into it? Most of the times, I think what I did in the past, and it's not just with the current thing, it's just in general how I work. The refactoring PRs were alive for longer. So it's not like we did the refactoring, someone just approved it and we merged it immediately, it was more like, hey, we're working on this. We're keeping it for longer until we make sure that it's the right time to merge it. So maybe it's right after an important release when we're done with the bug fixes or the QA part before the release. So it doesn't really affect the delivery of it. So maybe that's the right, the best answer for now. Just long, long-living branches that... Of course, I know they probably it's harder because they introduced conflicts and stuff like that. But still, it's much better than not having these refactoring branches at all.
And I think that's got a related question. I'm gonna pick one. Could you quickly share your view on how test coverage and refactoring culture are linked? Because one, obviously, testing kind of makes it easier to refactor but also sometimes harder because you also refactoring tests as you move in the core. True. Yeah, actually I had to slide on that and removed it because it just felt it didn't really match with the other things. But I did want at some point to also talk about how you make this whole process safe. So, in my mind, tests are super important, are crucial for this because you don't have the certainty if you don't have the right tests and let's say type safety. If you don't have type safety, it's very hard to refactor things, especially as the application gets bigger. I think this was the first thing that popped in my head like, oh, why, TypeScript is so amazing because I could now just start changing something and it should just propagate in all the files where the build was failing. And with tests, I think it's super important to have a variety of tests for refactoring. If you have just unit tests, most of the times when you refactor, you would have to improve those unit tests as well.
Refactoring versus Rewriting
If you have integration tests and end-to-end tests to complement that, then those should become your anchor. Always try to refactor and evolve the architecture and code base rather than starting from scratch. Please talk to Alex in the speakers' Q&A room for more questions.
If you have integration tests and end-to-end tests to complement that, then those should become your anchor, right? And you make sure that those are still working while the other ones get rewritten. Yeah, that's absolutely true.
So we have time for one final question. We have actually so many questions, so please do talk to Alex in the speakers Q&A lounge afterwards. Final question, very quickly, refactoring versus rewriting, what are your thoughts? Always trying to refactor. Rewriting, I think we went through a period of time, just like Nick was showing earlier, like the early period of front-end development when it was in a way better to rewrite because these practices, these best practices were reinvented on a monthly basis. I feel like now we're in a more stable environment with front-end development. Some would argue we're not, but I would say we are much stable than what happened like 10 years ago. I would always push for refactoring and evolving the architecture, evolving the code base rather than a revolution of just throwing everything away and starting from scratch. Absolutely.
All right that's all we have time for here on stage, but all of these questions here are great, well except that one, but most of them are great so please do and talk to Alex at the speakers' Q&A room. Thanks very much Alex for joining us and see you soon. Yeah.