When to rewrite code
It’s funny that the same thing that made Joel Spolsky write an article in 2000 makes me write an article in 2025, and I know, I’m not Joel Spolsky, but please, let me cook.
Unless it’s Rust
So here’s the thing. Today, more than ever, more people are obsessed with rewriting things „the right way”. Here’s an example: Ubuntu is actually adopting the new „oxidized” tools, and by oxidized I mean „written in Rust”, but they wanted to sound cool or something. Now, Rust is not the first successful abomination to come out of Mozilla-sphere, the first one being Javascript, but Rust offers an interesting proposition: when you finish writing your code you are guaranteed not to have a class of errors if you did not use certain patterns or you didn’t use a certain keyword or you didn’t use a certain number of libraries that are not yet written in Rust and that follow those certain patterns and use that certain keyword. We clear? (The normal Rust project has over 100 dependencies, a complex Rust project is said to have around 1000 of them although I haven’t seen a real-life complex Rust project, and I can’t confirm).
So this is a good reason to rewrite, write? I mean, right?
Because Rust fans are indomitable please add to each phrase that will follow the following caveat: „unless it’s Rust”. Not because Rust has these amazing merits, but because the fans are this level of annoying. We put this up-front so we can make sure that the fanboys are out of our hair. Now, let’s talk serious stuff.
The Netscape rewrite and how Microsoft has won
I hope by now you read Joel’s article. He talks about Netscape 6 being published while the web was developing. Netscape did the wrong thing, he argues, because the code base that worked, the one for Netscape 4, had tons of fixes and experience, and the bits of the source code don’t rot.
Only there’s a problem. Netscape 4 no longer worked. It didn’t work for quite some time before Netscape decided to start a complete rewrite. It crashed a lot, and what once was the way to connect to the internet was easily replaced by something that was included for free in your operating system: Internet Explorer. For more reference see the United States v. Microsoft Corporation case. Spolsky, a Microsoft employee back then, had all the interest into pushing a narative that the technical merits of Netscape is what took them down and not the monopolistic approach of Microsoft.
Now, I kind of side with Microsoft on this one. Microsoft was not wrong to package Internet Explorer with Windows. One should’ve expected to have a working web browser in their operating system; however, the price of zero is hard to beat for a corporation that only sells web browsers, therefore it was a hard time for Netscape and Opera . So I think that Microsoft was not wrong to package IE but was wrong in most everything it did in the business space otherwise, so I guess things even out eventually, but anyway. Back to the technical side of things, where we’ll assume that Spolsky is right and rewrite is the wrong thing to do.
Rule 0: You should never rewrite
Unless it’s Rust, you know the drill. So the point of the story is that one should never try to rewrite old code. There is a myth among the software development people that they would do things a million times better. They won’t. When a software developer tells you that they want a chance to write things from scratch, that usually means that they are pretty sure they can write code without a few mistakes they learned how to fix in the mean time. Translation for a business owner: this means that they want to do new mistakes that will feel fresh and invigorating. Just like Rust feels.
The most important part of Mr. Spolsky’s argument is valid. Source code doesn’t rot. There is no bit-rot in the code itself: that is language that can be translated by a compiler into processor commands, and that language is rarely changing. But if that’s where you wanted to end up, you would’ve read Spolsky’s article and be done with that. You want to know when is a good time to start a rewrite. And I have some ideas.
Rule 1: Rewrite when the technology stack changes
So let’s say you wrote some web server in LISP. You actually did it and it works great, however, you find nobody that can extend the web server code to actually match your business case (because LISPers are hard to find) - also, there’s no LISP interpreter for the target platform. You need to rewrite it in C++ or C#.
Not only the technology changes, but also the essential paradigms that make up the code change. Reuse is next to impossible. Obviously, you can keep the design of the old application, but some things might change there as well; in this case you really need a rewrite of the code.
Note, however, that even in a rewrite you can do things that are not a rewrite. And we’ll talk about these in a minute, but for the time being keep in mind that while some things can change in your product, and they can be rewritten, they shouldn’t break what already works. This is the rule for every and any rewrite. If you rewrite, you should end up with the same functionality as before (unless you really don’t need those clients anymore).
Rule 2: A rewrite is due when fundamentals of the implementation change
I’ve recently encountered a second possible reason why you’d want a rewrite, a reason I never thought I’d encounter. Let’s say that in your code base you made a fundamental mistake. I’ll propose an absurd scenario, but stick with me. Maybe you wrote everything in C++ but you decided that every function will return an error code and a value. So, if you want to return a string, you return a std::pair<ReturnStatus, std::string> and for everything you have to check if the return value actually has a value and if it has a value then you can use it. However, this makes your code-writing quite slow: you always have to check if the ReturnStatus is ok, even if there’s no point in testing this. So you have absurd code like (golang people, please shut up):
const auto [ret, val] is_ok = file_handle.IsOk();
if (ret.IsNotOk()) {
return {ret, "File Handle IsOk function invocation failed"};
}
if (!val) {
return {ReturnStatus::Failure, "File handle is not ok"};
}
// File handle is ok.
The chance of IsOk() failing is zero, but you still have to check it because you never know. Now, compare with:
if (file_handle.IsOk()) {
// do something
} else {
// error handling
}
Or, even better, by using exceptions, you can simply skip this test and write the code that uses the file_handle. From 6 lines of code to nothing, that’s quite a change. And that influences incredibly the maintainability of the code as well as the ease of understanding it. There are moments when you really want to do this.
So this is a net positive, and probably will super-charge your future code. So you probably should switch to the more natural way of doing things as soon as you can. And that requires, you know it, a rewrite. Again, see below how to do it.
Rule 3: You probably need a rewrite when your 10x engineer quits
… even if you don’t know it. You know the guy, a very opinionated dude who works on his own schedule, who is always moaning of the low quality of the rest of the team, who is always fixing things that broke in production, who never joins the dailies because he’s always too busy or not interested in your jabber? The one without whom everything will break down in a second?
Yes, you probably need a rewrite, because you really don’t want to fix the „insurance policies” he left in the code.
Rule 1000: But most likely you don’t need a rewrite from scratch
Let’s take these as corrolaries: you probably need a small rewrite if your design changes, or if some better way of doing things has been found, if you have 10000 lines of code in a function, if nobody knows how things are meant to work. But most of the time, even then you don’t need a rewrite from scratch.
How should you rewrite?
The most intimidating thing in the history of programming is File->NewProject, probably because this holds in itself millions of potential mistakes to be made, new ones and old ones, as well as a lot of forgotten experience from the previous project. Why did your software take that shape? How did it become such a cluttered mess?
So, if possible, never rewrite from scratch. Rewrite as little as possible, make islands of new code written with the new philosophy or the old technology that you integrate in the old code. It will take longer than to just rewrite the whole thing, but you’ll keep the good things (and potentially clients, but who cares about clients in this day and age?). You probably are looking for a refactoring; an evolution of the code instead of throwing the old branch to the trash bin and starting a new project.
Make your code easy to isolate. Build libraries out of your code, and define strict boundaries between them, so you can easily swap one component for another. Make things make sense.
Any rewrite, whether from scratch or complete, should start as soon as possible. A year ago would’ve been nice. Yesterday is fine. Today is ok. Tomorrow is already late. Once you came to the conclusion that a rewrite is necessary, the rewrite should’ve happened already.
Most importantly, you should probably rewrite with a very small team that breaks away from your main team maintaining the product. Rewrites should be done in small teams, that adopt some solid long term maintainable habits. You need to do better where you did wrong the first time. Document better. Write clean code, the way it annoys the current day internet folks. Write small components that can be individually tested. Do test (but perhaps nobody really cares about TDD, so don’t do that).
The most important part: decouple the rewrite team from the pressure of delivery. You don’t want a rewrite under pressure. You don’t want a rewrite that takes years either. So yes, Netscape did it wrong. However.
Do rewrite. From scratch
Netscape rewrote their engine in 1997-99ish. This code still powers the only consistent non-chrome web-browser out there: Firefox. Internet Explorer was not rewritten, it was scrapped and replaced by chrome. IE needed a rewrite that Microsoft decided it did not. In the end, Spolsky was wrong - the rewrite was the good solution.
The KDE team wanted a browser for KDE - so they made KHTML. KHTML is a rewrite from scratch, even if embedding Mozilla was a distinct possibility in 2000-something when they started. KHTML was then ripped off by Apple, published so they could get away without giving back, and picked up by Google. Now it’s what 95% of the web does. From a rewrite from scratch.
But you have to do it early. If you do it early, you’ll see dividends in the next 2-3-10 years. Have a plan for those years, but do it early to see the results at one point of time.
At this point, it would’ve been good to have had a Linux rewrite done in 2010. Wayland only now picks steam. SystemD is still hated by the old users of infernal-script-spaghetti that was SystemVInit.
And here’s what I like about the rust rewrites: they take modern approaches to old software. Things that were done badly before, that were created in a limited environment. Stop being constrained by the VT100 features from 1978.
And this is what I hate about the stereotypical rust rewrite. The fact that they are not bold enough. You can do new things. You shouldn’t be constrained by the VT100 features from 1978. Break away from those limitations. Rewrite it from scratch, in a modern way. Rewrite Linux completely if that is what stops you, just skip Linux altogether. Throw POSIX to the lions. Be bold.
And that’s why you probably should never do a rewrite.