Fighting Technical Debt With Continuous Refactoring


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.



Good morning, everyone. Wow. 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 Jani said, my name is Alex. I'm coming from Romania. I work at Code Sandbox. Anyone here using Code Sandbox? Oh, nice, nice, nice audience. Also, as mentioned, I'm organizing JS Heroes. This is a community event based in 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. 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. Why I want to talk about refactoring. Why is this talk about refactoring? Well, when I say I want to talk about refactoring, what I actually mean is I want to talk about refactoring culture. So 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 work, how to refactor our codes, our code bases without really impacting product development. Because I worked with so many different teams over the past years, and especially with product teams, and there's always been this problem of, okay, we have we accumulated technical depth. Now it's time to do refactoring. So 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 depth. And I think we can do better. I think we can build our engineering teams and our engineering cultures around introducing refactoring and just 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 picture provider. Picture is our engine for running the editor. So when you run the code sandbox editor, you have this picture thing that kind of serves you all the data from the VM. And we introduced a new way of consuming picture data. So now what we have is a legacy picture provider and a picture provider. 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 going to take them one by one and explain what I mean by them. So first off, we have practices. This is your goal, so to say, from the engineering perspective. You want to, you know, 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 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 code base 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, right? 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, you know, there are three ways. And just as Nick showed, there are three ways of doing something, right? Especially with react. Let's pick one. Let's have our way of working documented. We use at Code Sandbox this 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, well, 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. 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? 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 debt or have something that they know they want to refactor, they'll probably create a ticket. What I 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, okay, 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, not necessarily who will solve it, but rather who will raise the flag that, hey, 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. 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 it took us three or four hours or so, we just put there a bunch of dependencies, libraries that do all the things, we just hooked up stuff. We added some plugins, 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 code base, right? It's not something that, you know, developers open daily and have to struggle with it, 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 debt. But until then, probably we have other more important things to tackle. And finally, once you have your priorities 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 debts that are really tricky and maybe are multi-month spanned 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 debt. Here's the plan of how we are going to solve it. It's going to take whatever, three months, six months, doesn't really matter. We're going to try to stick with it, but we're not going to stop any other process. This is not going to block us because we have a plan to mitigate this debt. To recap these first two pillars that we talked about, it's almost like I said, practices are your goal. 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. I'm not saying perfect because you shouldn't necessarily aim for perfect here. And then inventory will give you these kind of gaps, 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 debt is, then process pretty much is the same thing as any other task, right? The only difference is that this time is 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 up front and will say, okay, we have to deliver this feature, and 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 up front, then you just do pretty much everything that you do on a regular task, right? 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 done, 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. All right. I found this also very important to kind of 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 kind of calling it like a framework for building this thing. Because 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 a common ground that the team aims for. It's not like 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 depth, what are you actually solving? Just 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 pretty much 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. The first one is to make it visible. 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 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 have clearly separated your tickets in your project management tool that you say, I want, you know, 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 the PR that handles like 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 that, you know, it's easy to contribute to any refactoring effort and it's also rewarding. So it's also celebrated. Right? Refactoring can be quite daunting because, you know, 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. It's pretty much the same feeling as if you just shipped an important feature. Because you shipped something on your roadmap. You shipped something from this refactoring roadmap that you have in parallel to the product roadmap. 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 codebase. 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 that you need 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. And product delivery will most likely get a higher priority and will most likely and likely tends to get other things out of the way. So when that happens, when you hit a tight deadline and everything, you need to make sure that the process is resilient. Even if it's on low priority, even if it's on low effort, it's still there. You don't lose track of it. You don't let technical depth kind of overwhelm the whole thing. What we do is we have these weekly engineering huddles with the entire team. 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? And 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, we still keep track of these efforts, even if it's just in the back of our minds. And of course, the last question is 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 code, maybe you're familiar with it, 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 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 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, right, than engineering. For engineering and normal everyday people, debt is bad, 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 a 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 that 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? Like 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 try to solve with the Marie Kondo efforts, right? Oh, there's this annoying router component, we have to go daily and it has some 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 I would pick one thing and just show how bad productivity is, just show, even 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, exactly. How would he understand the code anyway? Exactly. Feed them shit and keep them in the dark. Isn't that how we do? Alright, there's quite a few questions about prioritization 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 prioritize tech debt and how much time would you allocate on it in an engineering team? And I understand this is going to be different teams and different life cycles of the product, but is there a number that anybody could take home to their bosses and go, Alex Moldovan from Code Sandbox said that we should spend at least 70% of our time refactoring. Yeah, and I wouldn't want people to sue me later for giving them false numbers. I think it differs from team to team and also what I found, maybe 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. It's just distributed, so 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, it kind of puts the team in a more favorite position because you no longer have to think in like one person is out of the equation because they're doing the heavy work of refactor. But you do need to have things up front. I understand it takes time to, for example, document things and it takes time to even create a ticket, let's say. So if you have the process in place, it's much better. If you have all the things already up front, then just flagging another tech dev that was included or just anything else becomes more easier, like the entry barrier is lower to contributing to the effort. Oh, for sure. And even though we didn't get a number from Alex, I do 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 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. So refactoring with PRs is hard. What's on trunk-based development, feature flagging, etc.? Are there alternatives other than your standard GitHub PR flow that you can do to make refactoring easier? I can't 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, you're working towards 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 team, 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 best answer for now. Just long-living branches that, of course, I know it's harder because they introduce conflicts and stuff like that, but still it's much better than not having these refactoring branches at all. Yeah. And I think that's kind of a related question. I'm going to 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're also refactoring tests as you move in the core. True. Yeah, actually I had a 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 is typescript so amazing? Because I could now just start changing something and it would 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. 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. But yeah, 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 ten years ago. So I would always push for refactoring and evolving the architecture, evolving the codebase rather than a revolution of just throwing everything away and starting from scratch. Yeah, 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 talk to Alex at the speakers Q&A room. Thanks very much, Alex, for joining us, and see you soon. Yeah, thank you.
29 min
02 Dec, 2022

Check out more articles and videos

We constantly think of articles and videos that might spark Git people interest / skill us up or help building a stellar career

Workshops on related topic