 All right, we'll go ahead and get started. Hey everyone, I know some of you already, but for those of you who don't know me, my name is Brian Hughes. I'm a front-end engineer at Patreon. I've been doing a bunch of stuff in the past. I've also done a fair amount of node work. As part of my career, I've actually been a part of three successful, really complicated rewrites at companies. This is, of course, an open source conference, so I'm not gonna talk about any of that stuff, really. Instead, we're gonna talk about robots, specifically open source JavaScript robots. So really a long time ago now, back in 2014, I created an open source project called Raspbio. And this project is a plug-in to provide Raspberry Pi support to a much larger project called Johnny Five. Johnny Five is, or I guess was, it's not really used anymore. This project that enabled us to access hardware from on like Arduino's or Raspberry Pi's, things like that, using Node.js and JavaScript. It was actually pretty closely inspired by jQuery. So really popular project brought a lot of folks in. Of course, these days, we have different ideas on how we want to access hardware from JavaScript. You know, the world has evolved quite a bit. But it's been a pretty successful project. Now, when I created this plug-in, I created it basically over a weekend. It was like kind of one of those hacks where I was like, I just kind of want this to happen. I'm then released out to the world. Kind of became popular. In fact, this plug-in became the most popular platform for Johnny Five for a period of time, in fact. But as happens, whenever we write these things quickly over a weekend, it wasn't the best written thing ever. So in December of that year, I started decomposing this project into separate packages on NPM. There's, I think, about 10 or so of them total. And a plug-in like this really makes sense for decomposition because there's GPIO support that sort of sits in one corner of the code base. Then there would be serial ports in another and all these different pieces of hardware that just aren't really related to each other. Now, whenever this kind of decomposition happens, my project followed basically what all projects do that go down this path. We break off a piece, we clean it up. It's in really good shape, kind of sitting over here. We break off another piece, sitting over here. And there ends up being this one little ball of yarn in the middle. It's like that last bit of code you never really touched. And it kind of keeps going. It keeps aging, keeps getting a little more brittle, harder to maintain. And yeah, I got to one where I was kind of afraid to touch it because every time I did, I felt like I broke it. So this is kind of a problem. I was like, all right, it's time to rewrite this last piece. I've rewritten the rest of it. I converted ES5, which was the newest thing at the time. I covered it all to ES6 and newer, converted to TypeScript, except for this one module. So I was like, all right, I'm gonna do this. And in February of 2019, like a whole five years later, so not that long ago, I finally rewrote this core module. Once I had done it, the entire code base had been rewritten at some point in time or another. Now, the thing about this last rewrite though, I kind of mentioned this one specifically, is because before I successfully rewrote it, I actually failed at it four times. Like I mean, I created the branch, I start writing code, run into some kind of debt and regression, and it's just utterly failed. Had to throw the entire thing in the trash. Like I failed four times to rewrite this thing before I finally succeeded. And it wasn't because I got to be a better programmer, better coder, better hardware or anything. You know, I was already a pretty skilled coder whenever I started this project. The thing that caused me to succeed was actually about planning. And this was like the big lesson, is that a successful rewrite, it's not about your technical skills, or your coding skills really, it's really about how you plan the project. So for this talk, I'm mostly gonna talk about the planning process, how we think about this, how we approach it and break down these problems. Cause these are often like pretty large problems. But before we go into the planning, first I kind of wanna talk about the taxonomy of rewrites. So I have this whole talk title, to rewrite or not rewrite that as the question. Hamlet is my favorite Shakespearean playing, and I just couldn't resist to create this title. But it's a bit of a red herring actually, and a bit of an intentional one. Because when we talk about rewrites, both in this talk, but just generally, we talk about rewrites, we think of it as this binary thing. It's either we're gonna do it, or we're not gonna do it. But that's not really reality. Whenever we do a rewrite, it can mean a whole bunch of different things. So I kinda wanna walk through that first before we start talking about how to actually do this stuff. And so I kinda came up with this myself, but based on terms that I myself and a bunch of others have used before. And basically these go from like least amount of effort and scope to most. There's what I call a full rewrite, a partial rewrite, a heavy refactor, and a light refactor, with different effort and scope as we kinda go up to this upper left quadrant. Now the taxonomy actually implies two other types. We're not gonna talk about them in this talk, so they're not on this page, but the option is we're just gonna abandon the old project and create something brand new, or maybe just let it go, do whatever it's gonna do. I kinda think of like, you know when say Mattel created Fastify, Express already existed for Node, good project, but Mattel's like, I can do something totally different. So instead of working with Express, he created his own framework. I think that was a good move, but that's not a rewrite. So we're not gonna talk about that here. The other option not listed here is to do absolutely nothing, just leave it as is and live with it. It doesn't really fit in this taxonomy on this chart, but we're gonna talk about that a little bit here in a bit. But mostly when I'm talking about rewrites, I'm gonna be talking about these four variations, especially partial rewrites and heavy refactors, because I think that's what we mostly do. But what do I mean by this? So first let's talk about a full rewrite. A full rewrite happens whenever our new project, like once we're done rewriting, is not based at all on the old code. Like there's no code shared between them. This is we take the old code, we throw it in the trash, and we start from a blank canvas. Now these are pretty rare. Typically I only see these happens whenever we are completely changing languages. Let's say we're migrating something from Java to Node.js, or from the tooling world, what a lot of we're doing these days is migrating from Node.js to Rust. In that case, we can't share code because the language is different, the library is, everything is completely different. There's no way to share code. Unless you do say FFI or something, which I know a few of you have worked on here, but we're just gonna ignore that. But sometimes though, we don't have a new language. We might be going from JavaScript to JavaScript, but it still does a big enough change because say we're changing out a base framework. We have this library, maybe it works with a framework or an application built on a framework, and we're gonna shift that out. The core foundation of what we build on. So for example, when I did work at a company, okay, I lied, I'm gonna mention one company refactor I did. We had a backbone base, if you remember backbone, the UI framework back in the day. We had that we migrated to React. And the mental models are so radically different between them that we couldn't share any code. It's just incompatible conceptually speaking. And whenever we do these rewrites, usually a full rewrite, this almost happens in a new repo entirely. We leave the old one where it is and we create something brand new because there's just nothing to share. And not always, it's possible doing the same repo, but this is usually what we see. Stepping down the list of it, then we have a partial rewrite. And so in a partial rewrite, it's less than a full write, in this case it is based on old code. We're not throwing everything away. We are reusing some bits of code. This is always the same language by definition. And it's usually the same base frameworks as we are changing those concepts, but not always. This one may or may not be in a new repository. And this just kind of depends on personal preference. But when we're doing a partial rewrite, it's like while we are reusing code from the previous system, usually we're ripping out the foundation. We're kind of ripping out the bones of the old project and replacing it with something brand new. This isn't just touching bits and pieces. This could be something like in the node world if we have some networking middleware or something that's built on top of that, a server especially, and it's based on Express and we wanna replace it with FastPy. Back to that example. That's kind of ripping out the guts of this. But even if we aren't reusing code, the core nature of how this thing works is changing. But going down the list, then we have a heavy refactor, which again is based on the old code. It's the same language and is always the same base framework, but a lot of code is affected. This is one where the foundations aren't really changing, but the amount of code that is affected in a heavy refactor is usually on par with the amount of code affected by a partial rewrite. But the difference is that skeleton is saying the same. So the way I like to think of this is say we have an open source JavaScript framework that does a lot of asynchronous work. We could have the thing that's written all using callbacks. I'm sure there's a few of those that still exist out there. And we decided, all right, this isn't how JavaScript developers wanna code. So we're gonna convert this in a new major version to be promise-based so we can use async away. In this case, the basic foundation is the same, but that, as we can imagine, is gonna have ramifications for the entire code base. We're gonna have to end up making changes throughout the whole thing. And then finally, we have a light refactor, which, again, is based on old code, same language, same base frameworks, like a heavy refactor. But in this case, not that much code is affected. Oftentimes, light refactors can look like heavy refactors, but just in one little corner of the code base. It's just over here where we're affecting it. And the rest of it, we're kind of leaving as is. So this is kind of the taxonomy in the way I like to think about rewrites in different scopes and amount of effort and things like that. So let's start talking about planning. So the first step in any rewrite is to define the problem. This is a really, really crucial step, is we wanna talk about what is the problem we're trying to solve by doing this rewrite. It's really important. It's also one we kinda tend to skip a lot of times, I feel like. We really wanna say we wanna do this rewrite because XYZ. And when we do that, most important thing is to be specific. Because if we say, well, I wanna rewrite this code because the code sucks. I mean, that could very well be true, but that's not a reason, right? That's just, you're an opinion about a thing, it's expressing dislike. That's not a reason. We wanna say we want to write this because this part of the code base over here is really brittle and every time we change it, we introduce regressions and get 100 new bug reports. We wanna be specific on what our problem is that we're trying to solve. And not only that, when we're talking about these problems, it's important for us to think about describing the impact of this problem, not just an opinion or view of the problem. So again, I like this code sucks and is brittle. That's not about impact. That's, again, that's just an opinion on what we think of the code base. The impact is, well, we keep making mistakes and causing regressions and people keep filing bugs. We wanna talk about how does this impact the people who use our project especially? It could also be how does it impact the maintainers on our project. That's also an important part, especially since we always hope that people who use our project will one day become collaborators and maintainers. So if we have a code base where something is a spaghetti ball that's really hard for people to onboard to and we, thus, we aren't getting new collaborators on our project, that could be a valid reason too. But again, that impact is, we can't get new collaborators on our project. Not, this is a spaghetti ball and I don't like it. So we really wanna talk about that impact. And while we're doing this, it's really important for us to write it down. I think this is a really crucial step that can be kind of overlooked. Because at least for me, whenever I'm thinking about a problem in any kind of code base, I think, oh yeah, this thing over here, it's horrible. It's causing all these problems. And I have this idea that feels clear in my head when I think about it. But the minute I go to write it down, I realize, oh, I actually haven't thought this through very much. There's something about the act of writing it down that really forces us to get explicit about these problems that we have. And so this is what we really wanted to do to define the problem. And there's another reason we wanna do this, not just for the sake of having this, which we'll use later, but also if you can't define the problem, if you're struggling with these steps, it's really hard to articulate impact of this. Or it's hard to articulate specifically why something is a problem. This is a sign that probably we shouldn't do a rewrite. Because if we can't really define impact and things like that, it means this is more of a I don't like this as opposed to this is causing us problems. And that's a sign we should probably just leave it as is, even if it may not necessarily be ideal. Because doing rewrites, they take time and effort and that prevents us from doing other stuff. So I already answered part of the title of this talk. So this is how we know if we should rewrite or not. It's like if we can't define the problem, then we shouldn't rewrite. Once we've figured out our problem, we've really articulated this well. The next step is to determine our constraints. And I wanna start with a quote by Marissa Mayer. The company she ran at the time may not have aged well, but I think this quote has aged quite well despite that. It's that constraints shape and focus problems and provide clear challenges to overcome. Creativity thrives best when constrained. I think we tend to think a lot of creativity. We think if we have a blank slate, this white open field, that allows us to be the most creative. That's not really true, at least certainly not in my experience, both as a coder and as an artist, I'm a photographer, so like photo on the first page was the one I took. And I think it's also true in coding. There are so many ways we can solve any given project. If we don't sort of narrow the field of possible solutions, it can be really hard to know where to start and especially hard to find what is the best solution we can do given our constraints. That's why we need to define those constraints so we can actually come up with a better solution. And the first one to start with is to list intended breaking changes if you have any. So one of the big reasons that folks want to do refactors sometimes is there may be some new functionality we want to add to our project, but something about our architecture or maybe some core dependencies or something like that is actively preventing us from implementing that feature. And so we want to do breaking changes. And that's like the motivation for the rewrite, but we want to get explicit about this. We want to really write down what is it we want to change because this actually ends up being a constraint. And then this actually helps us with the next step which is basically the inverse which is to define our backwards compatibility needs. If we're doing a rewrite, this is some project that exists, we're obviously we want at least some backwards compatibility. If there's no backwards compatibility, we're writing a new project. This isn't a rewrite. But how backwards compatible should we be does kind of depend on the project. If you're doing a smaller project, something that's a little more niche like Raspberry IO that I wrote, we actually didn't have a little bit more leeway with breaking changes because we just don't have the sheer volume of people that we're gonna break. This isn't always true, of course, but like generally that's kind of been my experience. If you're a really large project, you typically have less tolerance for breaking changes. If you're some sort of mission critical project such as Node or jQuery, something like that, we usually have very, very little tolerance for breaking changes. And the reason for that is we really want people to upgrade. The more breaking changes you have, that's a barrier to entry and the more people you'll have not upgrade. And so especially in the world of Node.js and jQuery, like Robin was talking about earlier, we really need to get people upgraded because they're running on old versions with security vulnerabilities. And this is a serious issue on the internet. Something like jQuery runs on, see if I remember these stats, right? 77% of all websites, and was I think about maybe half or so or running version one or old or something like that. But it's a little scary, right? Because there are no security vulnerabilities in that. So whenever we're doing these upgrades, we wanna make it as easy as possible for our users to upgrade so we can get as many of them on board. And so we get that bit of attention between what we wanna move forward and how much we're gonna prevent others from kind of going with us. And so very closely related to this is we also wanna determine the consequences from regressions. So this is the big risk with a rewrite. In fact, this is the biggest risk, hands down. Whenever we do a rewrite, we put ourself at risk of regressions in a way that we just don't have doing normal development on a project. And the heavier of a rewrite we do, the more regressions we're gonna have. It's just the nature of the beast. We cannot prevent them. But there are things we can do to minimize them, depending on how much effort we wanna put into it. So we wanna think again, if this is something that's really mission critical, stability and lack of regressions is paramount. And so we have to go really slow, really steady and invest a lot in the rollout process to make sure we break as few people as possible. All right, so now that we've defined our problem and we have determined our constraints, it's time to actually start planning the project out and figure out how are we actually gonna do this? And one thing that the previous sections help with and the reason we go through this exercise is that they kind of paint the way for us. We can think of defining the problem is that is we're in a way defining where we wanna be. We're defining our destination. The destination is a world in which we don't have that problem. And then the constraints kind of help us to figure out what is the path to get to that destination. It's like filling out a map almost is the way I think of it. Our constraints tell us where we can't go and that starts to give us a little bit of vision of where we're going. And I found in practice, whenever I go through these exercises, I usually already have at least a rough idea of what the solution should be like just by writing out those things right there. As again, constraints, creativity. And so we think about this, we start getting ideas in our head. And with this, we can start thinking about how much code is gonna be affected. This is not something that we have to be really in depth about it. We're certainly not gonna go line by line through everything, file by file. But we can just take sort of an intuitive sense of we know we wanna get to this place. I think it's gonna affect a third of the code base, three quarters of the code base, something like that. So once we have a sense of roughly how much code we might think would be affected, and again, this is just a thought experiment, doesn't have to be fancy, then we can go back to this taxonomy and we figure out which of these approaches is gonna work best. Cause all these approaches really just kind of affect how much work we're gonna do. And you might notice I actually swapped out the access labels on this chart compared to the previous one. Instead of talking about effort and time, I'm now talking about risk and time. Because again, the more we go to the upper left of this quadrant towards the full rewrite, the greater the risk we're taking on. So if we're in a project where we have to be really risk averse, we really don't wanna get into that upper left. We wanna get as far to the bottom right as we can. We really wanna do as little as possible. This might sound counterintuitive in like, you know, we wanna do a rewrite and we wanna do as little as possible. That sounds like it's intention, but that's kind of the world we live in. And it is a tension that we have to balance. But once we have this, you know, we can figure out which of these works and that starts to paint us like, oh, should I create a new repository for this or do an existing one? Should we have a long-lived feature branch or not? All of those kind of like in-the-waste decisions of how we're gonna use GitHub to manage this project. Assuming we are using GitHub. And you know, I've seen projects do all of these. In fact, whenever I refactored Raspberry IO, I end up using heavy refactor, partial rewrite, and light refactor all at some point in time. They definitely all have their places. And in fact, so another example I kinda wanna talk about is for those of you who are familiar with CodeMirror, which is an open source code editor used websites written, of course, in JavaScript. For the latest version, CodeMirror6, he did a partial, or I think it might have been a full rewrite now, but I think about it, created a whole new repository because the maintainer, Marin, wanted to add some new functionality that he couldn't do before. But we wanna think about, again, it's like, what is it we're trying to accomplish? We figure out this approach. And then the last thing to consider as we're planning this out is can we do releases incrementally? And we really wanna do that if at all possible. Because we can think we have all of this work, especially in the heavy refactor or partial rewrite world, we wanna do all this code. It's gonna affect the majority of the code base. So when we ask, can we split that up into chunks? And this really is ideal. It can be more work to split it up this way, but in the end, it actually kinda helps us out a lot because the real problem, if we do especially a heavy rewrite, we release it all at once. We have the single version where all of a sudden most of the code is changed. Well, think about what's gonna happen as soon as we put that out into the world. People are gonna find bugs, right? Because there are bugs. So you're gonna get this mad rush of bug reports all at once. And even for large projects, there's still only so many resources to work with those users, validate the bugs and things like that. So if we can release incrementally, then we kind of, in a way, lighten our user support load a little bit. Because we're spreading that out over time. It can actually even help trace on bugs too. Because if we're like, oh, it was on this version, but not the next one, where we're halfway through our rewrite, we know it must have been something with that previous release. So we can narrow down what code we think might have introduced that as well. So all right, we've gone through, we've done our planning, we have an approach that we have picked out. Now it's time to actually start doing the implementation. And there's a lot of work we can do here to help that rewrite be successful. And I'm not gonna go too much in coding, because honestly the day-to-day coding of a rewrite looks like any other kind of coding. There's nothing really special about it, aside from testing. And so the tips for that, the first thing I recommend doing is craft solid types before you even start the rewrite if you don't have them. I'm not saying you have to rewrite your code base in TypeScript, because that itself is a rewrite. But most open-source projects, especially in the JavaScript world, there are types available, even if it's third-party through definitely typed. So we can make use of those. We wanna look and say, are these high-quality solid types? Or are they just kind of, so, so? And I've certainly seen both out there in the wild. The reason that this is really useful is if we can get solid types, we're accurately defining all of our API surfaces, we've accurately marked what's optional, what's not, that can serve as a template as we go through our rewriting, and we can kind of check our new implementation against what it's supposed to do. There's always that classic case of, oh, there's some property in an object that's optional, and we forgot to check if it's undefined. Simple stuff like that. This is where TypeScript can help us find that, but only if we have the right types to begin with. We can do the before after comparison. Very similarly, we should also implement comprehensive tests before we start the rewrite. Whether it's unit tests or integration tests, for the exact same reason, is as we're going through and we're running this, we can compare the old code to the new code to make sure that they actually behave the same. And this is something we wanna do all throughout. And you're probably thinking to yourself, isn't that just test driven development? And yes it is. So test driven development is something that I actually don't particularly use day to day. I think it has its place. Some people think it's a universal thing. What I will say is I think in rewrites, this is where test driven development's value really comes out. This is the sweet spot for a TDD. Because in this case, we are comparing two separate implementations. We wanna make sure they behave the same and we can use common tests as the way for checking that contract. And we go through, and if we have this in place, the rewrite almost comes a bit paint by numbers-ish. It becomes surprisingly boring. And boring is good. In fact, whenever I rewrote recipe.io, when I finally succeeded, this was the thing that enabled me to succeed. Those first four times I failed, I didn't have, or I think I had very rough types for that package, but I didn't have anything in depth. And I actually had no unit test at all for this project. This is not great. Hardware is a little different. It can be hard to unit test hardware because that's not software. You can't exactly mock a motor, you know? But I figured out a way to do it. And I spent a lot of time writing tests before I even started changing the code in that module. And this is what enabled me to succeed. And whenever I did finally release it, I think I only had about three regressions. Well, at least that anyone discovered. You know, I had a couple bug reports in, but it was surprisingly few. And it was this test-driven development method that really enabled this to succeed. Now, of course, this is specifically if you are doing a coding rewrite. There are other kinds of refactors out there, such as, say, swapping out CICD system. So that looks a little different in the details. But at a high level, we can think of it as the same. In, say, an infrastructure change, we can look at what are the artifacts before and after. We can start diffing files that are the output of our CICD and be like, did this change or not? Because they should, again, be the same. So we spent a lot of time. We've written this code. We're actually ready to get it out into the wild. Now, we're ready to do our release. But this is also where we need to be careful, slow, and thoughtful about how we're doing our release. And some of this is probably pretty obvious advice. But most importantly, we really want to do alpha, beta, and release candidates. We want to get this out there. We want to get people testing it to surface bugs early. And when we mark these things as this is an alpha release or a beta release, we're giving an indication of quality. And we're asking for feedback. Because the thing about a rewrite that's a little different than writing new code is we have no idea where the bugs can pop up. Statistically, they're not correlated with anything because we changed everything. So if we had a new feature, of course, we're looking to someone have a bug with this feature. But especially if this is a large project and you don't know where the bugs are coming from, you need a really lengthy testing process to try to surface that. Because you just need more testing. And so alpha, beta, release candidates, they really help a lot with that. Don't be afraid to use these and go through a lot of them. With code mirror, going back to that example again, which was a full rewrite. I think it was in a beta and the release candidate for two years, maybe? I don't remember exactly the dates, but it was measured in the order of years. But again, it was a successful one. I think it's a great project, by the way. Something else to remember is that Simver is more than just API signatures. We really wanna make sure we get Simver right, especially when we're doing a rewrite because of some of these extra unknowns and the surface area that we're talking about with a rewrite. And when we think of Simver, I think a lot of folks, we just tend to think, do we change API signatures? This is really important, of course, and we definitely wanna pay very close attention to how we're changing APIs. But this is just the start. And I think a lot of folks focus on this because it's kind of the easiest one. It's really easy to say, did we rename a function or a property? That's a very binary and obvious decision. But there's other things, too. Do we change our system requirements? For example, say we drop support for older versions of Node.js because we wanna use newer ECMAScript features. That needs to be a breaking change. And in general, anytime we do a major version release, we do that because there's a chance we will have broken someone's code in known ways. Regressions don't count. There's always a chance of regressions that can happen on a patch release. But if we know there is even a chance that someone will break, we have to do a major version release. But even beyond system requirements, because even these are still somewhat obvious to reason about, the next one is behavior changes. And these are the ones that can be the most difficult, I think, to identify, but this is really critical. And as an example, this is where I think React did a really good job recently. So they released React 18, I forget how long ago, six months a year, something like that. And this was a major change. One of the biggest changes they have actually made to that project since it was created. They did a lot of fundamental changes to how the core rendering engine works. As well as, you know, so they were thinking like, how do we do this? How do we do this upgrade? So they actually released an intermediate release, React 17. React 17 was kind of a no new features release. And that was on purpose. But it's still a major version upgrade. And that's because there was no changes to the API at all. They didn't even add anything, I don't think. So the API was exactly the same. I don't think they changed any of the system requirements, if I remember right. What they did change though was behavior. It was a very subtle one, but an important one. So in React, this is again, web technology, so excuse me if y'all aren't web folks, this might be a bit lost. But in React, there's event handlers that it adds basically to the root of its own space to fire off various events because we're bubbling up and down the DOM. And so in React 16 and before, those event handlers were actually attached to the body element. So kind of the root of the DOM that the browser actually creates for us. And React 17, they changed it. So it's instead attached to the div, the root div that it's rendering inside of. Sounds really simple. It's exactly the same events. They have the exact same order. They have the same data. But just that little change of putting where they're located actually raised the possibility that they could break folks. And in fact, at Patreon, when we went through our upgrade to React 17, we did have a few breakages because of this. But we need to look for them. Because again, they were really good about saying, hey, this is a break and change beyond the lookout. So we went and looked for it. We found, I think, three or four places. We fixed them in about a day and we were good to go. It was a really painless process. But that's because React put in this effort to identify what the break and changes are beyond just API signatures. So really related to that, it's also really critical that we write good change logs and upgrade guides. This one may seem obvious, but I've encountered kind of a scary number of open source projects that have no change log whatsoever. I'll say, oh, there's some major version upgrade. What broke? And I look, I have no idea what changed. I have to go look through commits. Most people aren't gonna do this. And even people who do go through look commits, like I sometimes do. I usually skip it, but when I do, even then it's really easy to miss stuff because I don't know the projects. I don't really know the impact of the code change or anything like that. So it's really, really critical that we write out a change log saying, here's what we changed. Here's what we broke and how. Especially for this behavioral stuff. And when we're doing a rewrite, it's also really important. I mean, this may sound like, why would we bother, this seems a little silly, but we really should talk about here's the code we changed, even if it's only at a high level. We can say, we rewrote this, all the code behind this function here. And the reason we wanna do that, especially if we're doing an incremental release, is that we're pointing our users to an area so that they can be aware of a change. Because we wrote through this, there could be regressions that we don't know about. And if we say, hey, we rewrote this part of the code for users who are using that specific function, then they can look and say, hey, did this thing change? They'll know to test that a little more. Or also, if they're like, oh, well, we don't use that function, then they can skip that testing and they can just kind of go about the day and they can save a little bit of time. And they'll be more likely to get that next upgrade whenever they do know they need to put in time. But also, it's more than just change logs. It's also upgrade guides. Change logs, I kinda think of them as like an API reference. They're very specific, they're very technical, they're kind of in the weeds. They list out all these details. It could be hard to get the big picture. And so an upgrade guide is also really important. And this is where we talk about, here's why we did these upgrades. We can give context. And since we went through that whole exercise at the beginning to really define the problem, we can just link straight to that. We wrote it down, presumably in, say, an issue or a markdown file somewhere. So we can just link to that. And this gives people even more context of here's why we made this change. And hopefully that can help people get a little excited. Especially if you can give a glimpse of like, we made this change because we've got this really cool thing coming in the future. Like, you're gonna love it when it comes out. And we're gonna wet that appetite a bit in an upgrade guide. It's kind of a fun thing to do. And also, this just kind of helps guide people along. Because again, whenever we do these rewrites, we're doing them so that people will adopt it, right? And if it's hard to do, people aren't gonna do it. And so something I like to say about documentation is we can write the best code in the world. We can have the greatest API. But if it's not documented, it doesn't exist. So documentation is, I think in some ways, the most critical part of open source. But also oftentimes the most underfunded and kind of underappreciated, which is unfortunate. But all right, so we've gone through this. We've written our code. We've released to the world. People are using it. So I wanna kind of zoom out a little bit. Because I am at least hoping that the things I've talked about here aren't all that novel or new. I hope this is at least somewhat obvious things that you all have seen before. And yet, so I've seen a lot of rewrites fail. I failed myself a couple of times. And interestingly, at two of the three companies where we did a rewrite, I happened to join just a couple months after they had previously tried to rewrite and failed. And so then I came in and in the second attempt is when we made it successful. And I've realized that success in a rewrite, it's not just about writing good code. It's not just even just about following these steps. It all comes back to why are we doing this? How are we thinking about it? And what is our mindset? And so the first tip around mindset I wanna give. Also, classic saying is cliche at this point, but it's so true. And that's don't let perfect be the enemy of good. You know, when we go in it and we do a rewrite, we have this sort of instinct and desire to make it perfect. You know, it's like, this is the chance to do a clean slate. We're gonna fix all the things we hate about our old code. It's gonna be amazing. It's gonna be perfect. It's gonna be performant. There'll be no bugs. Everyone can write it. It'll probably cure cancer while we're at it. The way people talk about this stuff. But that's not how it works in practice. Every rewrite will always be imperfect. It will always have issues and flaws. And we just need to be okay with that. We need to accept and embrace that that's part of the process. Because if we don't, that's how we end up spinning our wheels. We spin forever on this. This thing never actually gets released. Or if it does, by the time it's released, the whole project is, you know, obsolete or things like that. And then the next question again is, do you even need to rewrite a refactor? And I talked earlier about how we can kind of think about what will tell us do we need to do this or not, right? Again, it's like, what are our reasons? What's the impact? We need to be really, really critical about this. Because like as engineers, I think we're just kind of predisposed to want to rewrite. You know, I know I certainly am. You know, I've been in code bases where I've been like, oh, this is awful. We should have spent time rewriting. But then we usually with the help of good managers, at least in the past, before I started thinking of this way, they would say like, oh, let's step back. Let's think, all right, what is the impact? Why do we, why should we actually do this? And so we need to be really critical of these kind of like, you know, motivations and desires that we have around instincts. And ask, do we really even need to do this? And actually I set back even a bit further and ask, is like, why do we rewrite? And I mean this like at an existential level, why do we as engineers want to do rewrites all the time? It seems like we're always talking about rewriting. Like where does that come from? Like what is that desire? What pushes us to do that? And I think there can be a lot of reasons to do it, right? It does depend. But I think one of them is, it's this kind of emotional response. It's, I hate this code, especially if someone else wrote it, you know, not us. But if someone else wrote it, sometimes us too. I've looked at my old code and I hate that. But, you know, we look at it like, oh, this is so awful. I could do such a better job. You know, I can get this out there. It's gonna be amazing. And, you know, I'm gonna be amazing. You know what I'm using a lot of I statements. Right, when we get this desire, a lot of times it ends up because, kind of because of ego in a way. You know, this is something that we want to do better at. You know, and it's not necessarily we want to show off what we can do. It can also be because we are unhappy with what we're doing. But it's all about this kind of thing. It's like we just want to come out and do it. We don't really think about, well, what's the actual benefit here? You know, we don't think about others. Right, it's I statements. So really what I think when it comes to rewriting, like the most important thing with mindset is that we should rewrite to serve others. We're doing this rewrite to help. Right, we want to do this to make it easier for our people to use our project or easier for people to come on board and become collaborators on our project. Or we want to do this to give new power to the users of our project, to create amazing new things they couldn't do before. It's ultimately about enabling and empowering other people. If we really keep that at the core, we're thinking about rewriting, we're thinking about how we do it, how we go through it. I think that's the real way to success there is to think, how can we make other people's lives better? Not just ours. With that, I want to thank you for listening. Right, it looks like we have a few minutes. So if anyone has any questions. I think you might have been first, but yeah, we'll go with you. Thanks. Kind of a question. Oh, sorry. But at the same time, I think another really good reason that crops up is that we work in technology, which changes every day. And so as we are learning, right at the end of writing something, you might have a better way to write it already. So going back to something that's old, that's yours or somebody else's, I think it's just a, hey, I've got more experience now. I could make this better. So I feel like that comes up for us a lot, but to your point of the value, you need to have the value there because maybe it is just a refactor of one section that could have a lot of value versus the whole thing which is probably not worth it. Yeah, yeah, absolutely. And I think you nailed it. And I would even take it a step further. I was like, if we're doing a rewrite without changes to it to enable something else at least immediately, like if we're successful, no one will know we've done anything at all, right? And so that kind of gets us to this question of like, well, what does this actually get us if no one even noticed, right? We hope it's because we now get to go do something else really cool, right? It's we're enabling something else. First, thank you for the presentation. And I would say you took the main point. And as a remark, I would insist on what you say, like the testing part when you are in implementation. I added full reward for my company. And we were lucky in that case. The point was not to worry about lot of integration testing, unit testing, shadow traffic of the client and everything. And I can say to you, it's really a game changer. If you have no test and I think you saw with your experience, first three times, no test or not enough for all the guys really be sure that you have one, you want another that say red or green. If you don't have that, it's a failure. Yeah, yeah, absolutely. Especially in this case, because again, this is either the test passes or it doesn't. Like this gives us a very clear indication of did we port this right? And so yeah, we need to surface that. This is, we need good CIC integration in our source control to tell us that, to give us that pass bail, definitely. And I would say sometimes you can just maybe don't need to write the test. Often you can just get it from existing usage if you can. Like if somebody use your application or your IPI or whatever, think of way to capture what they do. And more or less you don't even need to know if it's even a bug because maybe you want to reproduce that bug in a sense because it's still a feature change if you don't have a bug anymore. So you want to capture that and it's a great tool to perform immigration. Yeah, so if I'm following you correctly, you're saying this is also really basic observability is what we're talking about at this point. We have really good observability. So this gets more into the product realm. It's kind of why I didn't go into it for the open source world. But depending on telemetry, you might be able to do this here. But certainly in the company, we've got a product for end users world. Observability is really critical too. We want to have lots of instrumentation of how people are using our apps so we can look at that, especially if we're doing like an A-B test or something like that between these two, then we can look and say like, are our analytics looking the same between the two? And if not, that can be a problem too. I don't think we see that as much in open source. Again, it depends on the project. You might be able to get that instrumentation. VS Code has it, for example. But yeah, it's very powerful, especially at companies. Yeah, thanks for the talk. You mentioned that for package authors we're providing a way to upgrade from one version to another when you rewrite APIs and things. One suggestion I would also make is like provide code mods. They have been proven to be very useful. And once you get a hang of writing asked modification, like it takes a little bit like learning curve. But if you have a tool that you can just run and it updates interfaces of all files, that has been proven to be very powerful. Yeah, definitely, code mods are also great. And in fact, if it's something we think that my code mod would be useful down the road, we can even think about that early on with our breaking changes to make sure that we change the API in such a way that a code mod would be able to detect it, right? Yeah, it's not ambiguous. JS Code Shift, I've been using JS Code Shift for most of the time. We use it at Patreon as well, along with the sMorph. They're great tools. All right, and I think we are about at time. Again, thank you everyone for coming.