Published on

Rewrites: The perils of not knowing what you don't know

You are dealing with an awful legacy codebase. It was not properly designed or maintained, there was no clear direction when it was built, and everything is held together with duct tape. It seems like it’s about to collapse at any point. Fixing a bug or adding a feature ends up taking so much time that you think of what choices you made in life that make you deserve to live through this hell.

The thought constantly comes up. Is all this effort better spent on just throwing out this awful system and building a new thing? Tools have improved considerably since this repo was started in 2000 and late. How hard can it really be?

Careful now, you are entering a danger zone!

The trap of ignorance

You start from scratch. It’s a breath of fresh air to not have to deal with the awfulness of the legacy codebase. You don’t want to repeat the mistakes of the poor souls who built this monstrosity so you are doing your best to find out what’s best practice.

You are now falling into the first trap!

It seems that the answer lies in what others, especially big companies, do. Maybe it’s a microservice structure. Maybe it’s a certain stack. Could be a design philosophy. Could be a language. You read blog posts about how they solved issues (at scale).

This is the trap: What works for them won’t work for you. I mean it may, but nearly always it doesn’t. Why? Because the problem they are solving is not the problem you are trying to solve. If you take a step back you’ll realize this. Work from first principles, and figure out what will be less effort. Maybe a refactor, as painful as it is, will do wonders.

But, you’ve already fallen into the trap. Everyone is excited, everyone is on board. Legacy is boring, greenfield is fun. Time to get to work!

The trap of sunk cost

With a bright future, you have been working on pouring a new foundation. You got to a pretty good place by now. Sure it’s been a while, some things did take a lot longer than you expected. But you got to think about scale and avoid the problems of the legacy codebase! That legacy codebase is a convoluted mess.

You just have to get past this painful transition, and tomorrow will be better. You’ll be able to move faster and easier when this is done. Just implement the features and you are good.

Here lies the second trap!

It’s easy to blame others with the benefit of hindsight. Yeah, the ones who engineered the legacy codebase obviously didn’t know what they were doing. But do you? Why is it so difficult to maintain it in the first place? In the attempt to make life easier, are you actually adding more complexity to a problem? Did you ask the right questions at the start? Is your focus on the problems you want to avoid blinding you from the problems you are creating?

With significant time invested, and expectations made, it’s very hard to pull the brakes. If you didn’t take a step back, and work from first principles, how would you know what problem you actually are trying to solve in the legacy codebase? By now you are going down a path where you’ll have to deal with legacy business logic in a new system.

All the experience built into the legacy codebase will have to be extracted. And you are going to miss something. Because the old codebase didn’t have exhaustive testing, no good boundaries, logic all over the place, the list goes on. It will be very difficult to confirm if you’ve implemented a feature correctly.

Well, we can’t ponder long on this. Got deadlines to make, impressions to make - most of all to yourself!

Blowing up

As time goes on this may blow up in your face. It becomes too difficult to reach an acceptable production state. Unmaintainable, it’s just dealing with two monstrosities instead of one. At least the first one did kinda work. The new one seems to break down in random places as network data is fed into it. It’s painful.

You are burned out.

Fizzle out

It may also just fizzle out. It works, has some kinks, and you do your best to keep it running. Until you no longer are maintaining it and now a new person or team is complaining about how this codebase was built by idiots. Because you had to support weird legacy business logic. You ended up in a no better situation than before embarking on this.

What did we learn?

Not a damn thing. We dug our own grave because we didn’t take the time to assess the problem at hand. It may not even have been a problem, or it was an entirely different problem to begin with.

Lessons learned by experience. We don’t know what we don’t know. The dragons were out there but we didn’t even know there were dragons! At least we didn’t spend too long… wait, it took over eight months?! Oh, oh no.

“But wait, it did work out for me”

Congrats! You got to the other side. Maybe you assessed the problem right. Maybe you knew what you were doing. Maybe it was dumb luck that you didn’t fall into the pit. Would you make this gamble again?

How to approach rewrites

Much has been said about rewrites. There are books and blog posts that will go much more into the details and better highlight how to approach rewrites. These are a few things I usually think about when I approach the rewrite itch:

1. Know your stuff

  • If you are very comfortable with the stack (e.g. “choose boring”) you will prevent quickly drowning in issues. The work required will still be pretty painful.
  • If you have the experience and introspection to assess the problem, and understand what you really need to solve, you can confidently figure out if a rewrite is worth it. Understanding the emotions behind the itch is important.
  • If you neither have the experience nor the introspection, then it’s preferable to just deal with the current codebase. The resources are better spent building new features and repairing broken windows.

2. The code is not you

  • Be ready to throw away all the work if it doesn’t solve the problem.
  • Be ready to throw away all the work if you can’t solve it in an acceptable timeframe.
  • The code is not you. It’s natural to be emotionally attached to your work, but results should be what guides you.

3. Results are the only metric

  • Instead of solving big problems, solve small problems. You’ll deliver results much faster. And it may help you identify and tackle bigger underlying issues.
  • Set appropriate constraints. An attainable hard deadline will help define the problem to solve.
  • Double your time estimates. Be very careful if it will take more than a few months. Often that can be a sign there is not sufficient understanding of the process.

Hi, I'm Dan Schultzer, I write in this blog, work a lot in Elixir, maintain several open source projects, and help companies streamline their development process