DEV Community

Refactoring a Legacy Project

Itachi Uchiha on November 22, 2019

Hi, I'm starting a new project at work. We have a legacy project which has really bad codes. This is one of our big projects which still in use. S...
Collapse
 
avalander profile image
Avalander

I've spent a good deal of the last year refactoring a few poorly written backend services. Here's how we did it.

Start where you make changes more often

You probably will have to build new features and fix bugs. When you have to work on an area of the code, start by refactoring that area and then implement the new feature. The point of refactoring code that works is to make it easier to add new features in the future. If you don't need to touch that code for the next five years, spending a week refactoring it is just waste.

Add integration tests first

When you start refactoring an area of the code, the first thing you should do is add tests to validate the current functionality. That way, when you are done with the refactoring you know that you haven't broken anything.

Measure code improvements

Usually, our definition of legacy code is code that the previous developer wrote and I don't like, which is quite a weak argument for spending time rewriting it. If you are going to spend a large amount of time refactoring code, you should measure that you are actually improving it, and not just writing code that the next developer will want to rewrite.

We used Sonar Qube's cognitive complexity as a guide to see if we were actually decreasing the complexity of the code we rewrote. It is not the only way to measure quality improvements, but I can't stress enough how important it is to have a somewhat objective measure of how your codebase is improving.

A warning on rewrites

Rebuilding a project from scratch is very tempting for us developers. It's tempting because we get total freedom to build it in whatever way we want, we get to make all important decisions, and use the tools and frameworks we like the most. However, I have never seen a rewrite from scratch succeed on any codebase larger than 30k lines of code.

The problem with rewrites is that they take way longer than you think they will. While you are rewriting the project from scratch, you are not adding new features and fixing bugs in the old codebase. Besides all the business concerns with not building new functionality for a long period of time, there are a few issues from the development point of view.

The main problem is that your users will not switch to the new thing until the new project has feature parity with the old one. Which means that you will not get feedback on whether you are building the right thing or not in a long, long time. If the time comes that you are actually ready to replace the old thing with the new, issues start to flow because the old project had evolved to handle hundreds of corner cases that you didn't account for.

Everybody is frustrated that the new thing doesn't work as it should, management gets nervous. You start adding hacks and duck tape because you realise you hadn't designed the project to handle certain cases and it's too late to change early architectural decisions. Finally, you burn out, you quit, and the next developer who inherits your codebase starts talking to management about doing a complete rewrite of the project.

Collapse
 
itachiuchiha profile image
Itachi Uchiha

I'm using Sonarquce in a different project. It is really useful.

All the things you said are right.

I can't add a new feature or I can't fix a bug. Actually, I need to rewrite because the invoice module has many bugs. The sales module has many bugs.

For example, there is a variable like that;

String a = "";

a = a1.ToString();

I don't know exactly what this variable does.

Collapse
 
avalander profile image
Avalander • Edited

That's why we write tests before refactoring a piece of code. That way we can be reasonably sure the new code is not critically broken after the refactor even if there are things in the old code that we don't understand.

Plus, we are able to deploy the new code to production quickly and the users start using it, so if we missed anything or changed some behaviour, we will get feedback on that quickly. If you do a rewrite of the entire application before your users start using it, how long will it take until you realize you missed something?

I get the impression that you are more confident in doing a whole rewrite than refactoring bit by bit, but I wonder why that is. If you don't feel confident to refactor an area of the code because you don't know what a variable does (that's what I understand you are saying in your comment, please correct me if I'm wrong), why do you feel confident you can write a whole new application that will fulfill all the requirements of the old one?

Collapse
 
murrayvarey profile image
MurrayVarey

@avalander has given great advice. Just to add (and maybe repeat) a few points:

Legacy code is valuable

Legacy code is already out there in the world, helping people's lives. Working on a legacy project is exciting because you are improving on something that is already useful. I find this thought reassuring when in the middle of wrestling with legacy code.

Don't rewrite (unless you absolutely have to)

Avalander has already covered this in depth, but it's worth repeating: Rewrites usually fail.

Your existing codebase is a document of decisions that have been made about your project. A lot of these decisions may seem bizarre, but most will be there for a good reason. If you throw that away, then you have to make those decisions all over again. This takes time. Meanwhile, you're old codebase is dead in the water, while you're playing catch-up.

There are some famous articles about this, notably Things You Should Never Do Part I, by Joel Spolsky.

Yes, there are probably times that a rewrite is appropriate. However, this is not a decision to take lightly. If in doubt, favour refactoring.

Refactor separately

Refactoring and adding features/debugging should be kept separate. They require two completely different mindsets. When refactoring, your biggest concern is not changing behaviour; when adding a feature, your biggest concern is changing behaviour. Mixing these up will cause a lot of confusion, and make them hard to juggle in your head.

My process tends to be:

  1. Find the area of the code that I'll be working on
  2. Create tests (if they don't exist)
  3. Refactor, until I understand the code well enough to change it
  4. Finally begin work on the feature/bug

The refactoring stage is crucial here. Often the answer will just seem to fall out as I'm refactoring.

Make small changes

One thing that I've found useful when refactoring is keeping my changes small. It's much easier to manage lots of little changes, as opposed to one huge change.

This was inspired by Practical Refactoring by Llewellyn Falco and Woody Zuill (which I mentioned here). I'd recommend that video to anyone working on legacy code.

Best of luck Ali!

Collapse
 
avalander profile image
Avalander

I didn't know about Joel Spolsky article, it was an interesting read, thanks for sharing!

Collapse
 
murrayvarey profile image
MurrayVarey

You're welcome! His blog is probably the most informative I've found on programming. A lot of his posts are old, but stand the test of time.

Collapse
 
ujwaldhakal profile image
ujwal dhakal

Depends what your product is really about ? What are the customer bases ? Figure out critical featues that customer uses ? Figure out the low hanging fruit and start moving low critical features into different microservices step by step and mean while supporting legacy code bases. If its a huge legacy monilith code and you can to rebuilt a monolith again welcome to the recursive my friend . Lets get back to past when legacy was built again :D

Collapse
 
itachiuchiha profile image
Itachi Uchiha • Edited

It's a huge legacy monolith code :)

My managers don't want to rebuild. If I were them, I would rebuild.

There are critical features like payment, appointment, etc.

There isn't any comment about methods, etc.

Imho, this will be my best work :D

There is a code like that;

bool isExists = true;
if(isExists)
{
  ... logic
}
Collapse
 
ujwaldhakal profile image
ujwal dhakal

Looks like i needs microservices . What you can do is make a prototype of working microservices feature from existing one show him , it may motivate him to do microservices

Collapse
 
aminnairi profile image
Amin

I would suggest trying to stabilize the current application so you have some time to make some architectural and structural changes like splitting the app in micro services, separating the front and the back (for a web app for instance), etc... If you work alone, the stabilization process will help you continue providing a service while working on the next changes.

Collapse
 
franksierra profile image
Frank Sierra

Don't rebuild, that's the wisest choise.
Read @avalander answer it's on point.

Collapse
 
mkubdev profile image
Maxime Kubik

I'm in the same situation. I think i'll rebuild everything. (It's a vanillaJS WebGL engine). But i dont know where to start ...