Back
Jun 27, 2018

How to Work With Legacy Code: Code Refactoring Techniques

Vladimir Sidorenko

Here I just want to describe the general philosophy of working with old code and the attitude that I think must be adopted. Everything written here I quite simple and obvious for anyone, who have already worked on at least one legacy project.

Apart from two simple examples, you won't find specific language dependent refactoring techniques. For those I highly recommend Working Effectively with Legacy Code and Refactoring: Improving the Design of Existing Code

Accept it

Your legacy code is yours now

First thing that I think we must accept when given a legacy codebase to work with - is that now we own this code base. We are responsible for it and there's nobody else who can relieve us from it. Also, we should never forget that our main task is to make project successful, not to write clean code. Clean code is just a tool that helps us create better projects.

When the codebase you are given is just a mess and is written against all the best practices - it's still a tool. Focus on thinking how to make project better having a tool that is available.

p1tz5bu8fdj01.jpg

Don't rewrite everything

Of course, this is the first thing that comes to the mind when you open your newly adopted legacy codebase. But this is not the best solution. Joel Spolsky has already explained this a lot better than I ever would, so just look through his article if you haven't already.

Create a plan

I've seen a lot of developers trying to distance themselves from the legacy code. When asked to extend existing code, they open a file, close their eyes and just write something in the middle. They think that the legacy code will be removed eventually and there's no point in trying to learn it's internal structure and extending it in a future-proof manner. Other guys do the opposite thing - they write new code to play well with existing structure. Since existing code is ugly, they continue writing ugly code. Their reasoning is somewhere along the lines "I can't write clean code, because it won't integrate with what we have"Both of these end up with a mess. You can blame unknown authors of the legacy code, but you can only do it for a limited time. On the first week it's understandable (though counterproductive), after a year - nobody will care that there were some other developers before you. You must plan exact actions that you will take to improve (or replace) existing codebase. Otherwise you'll end up with an ugly codebase, with nobody else to blame.

Do the right thing

Write tests

Well, duh. Without tests you'll be afraid to make any radical changes. Or, even worse - you won't be afraid. I think, that granular unit tests are not required here, a high-level integration tests that treat whole modules as a black box - just call it and check the result, without going deep into what exactly has broken and why.

Sprout pattern

When writing new code, though - it's a good idea to write a unit test for it. Some might wonder - how to write a unit test for a piece of code that needs to be injected right in the middle of a decade old function of 500 lines? In this situation you can create a new method or function with new logic, cover it with test and then just call it inside of that giant function. Adding more lines to an already enormous function is not the brightest idea and sprout approach is a good thing anyway, but it also helps with tests.

Coverage

Measuring test coverage is always a good idea. When working with unknown code, it's even more important. Even if you don't have enough tests, just knowing which parts of the system might be unstable helps a lot.

Create correct interfaces

Interfaces in OOP terms I mean. When using badly architected interfaces and APIs, one might find himself in a vicious circle:

  1. New code is created with bad architecture, because it has to work with old code, which is badly written
  2. Old code is removed
  3. New code that replaces the removed parts is now also written with bad architecture, because it has to work with the code, added in the first step, which is forcedly ugly.
  4. Here we are - there are no traces of the old code anymore, but the overall architecture is still bad.

I think that all the new code you write must be clean and use all the best practices, regardless of what you have in the system right now. Instead of thinking of the simplest way to solve your current task, think of what would be the correct architecture of the overall system. Write the new code as if other parts of the codebase have that correct architecture.

ne5x8P8.png

Use adapters and facades

Hide imperfections of existing system with Adapter and Facade patterns. Then just throw away adapters along with the old code. Adapters themselves can be as ugly as needed inside, but they have to provide clean interface. For example, on one of our projects we were forced to integrate with a third-party service API to pull some data. Their API was quite strange and was lacking one of the needed endpoints. We have written the ugliest piece of code ever to login and basically crawl their service and package data in the json. We have written it in such a way, that it looked as if we are calling an actual API. Pretty slow, but still a single API endpoint. Under the hood it did all sorts of hacks - authentication, clicking through pages, retrying on crashes, parsing html.. but on the outside it was just a RESTful endpoint. When API had been fixed, we just threw away the adapter, but we didn't have to change anything in the existing code.

Make it better with each step

The main rule that covers this entire topic is following: with each step you take, make overall quality better. You should not allow your new code to be badly written. Don't be lazy. Everything you add to the project must make it better. Specific technical solutions for this are usually easy to find, when you actually look for them.

More thoughts

Sep 23, 2010Technology
OR and AND without django.db.models.Q

I just found out that __or__ and __and__ are defined for QuerySet. This means that to do queries union or intersection, you can do:User.objects.filter(...) | User.objects.filter(...)User.objects.filter(...) & User.objects.filter(...)

Vladimir Sidorenko
Apr 19, 2022Technology
Improve efficiency of your SELECT queries

SQL is a fairly complicated language with a steep learning curve. For a large number of people who make use of SQL, learning to apply it efficiently takes lots of trials and errors. Here are some tips on how you can make your SELECT queries better. The majority of tips should be applicable to any relational database management system, but the terminology and exact namings will be taken from PostgreSQL.

Yurii Mironov
Sep 1, 2021TechnologyBusiness
Top 10 Web Development Frameworks in 2021 - 2022

We have reviewed the top web frameworks for server and client-side development and compared their pros and cons. Find out which one can be a great fit for your next project.

Marina Sharapova
Apr 3, 2011Technology
Sprite cache invalidation

When we use css-sprites it's important to make browser cache them for longest period possible. On other hand, we need to refresh them when they are updated. This is especially visible when all icons are stored in single sprite. When it's outdated - entire site becomes ugly. To solve this task I've implemented this small script that adds file's hash to url:background-image: url(images/icons.png?a3844c660);

Vladimir Sidorenko
May 12, 2022Technology
Increasing performance by using proper query structure

Earlier in our previous article "Improve efficiency of your SELECT queries" we discussed ways to profile and optimize the performance of SELECT queries. However, to write complex yet efficient SQL queries, there is a thing to remember about.

Yurii Mironov
May 12, 2010Technology
Twitter API, OAuth and decorators

In my current project I had a task to use twitter API. Twitter uses OAuth for authentication, which is pretty dreary. To avoid fiddling with it all the time, I've moved authentication to decorator. If key is available - nothing happens, just view is launched as usual. It's convenient that there's no need for additional twitter settings in user profile. Code is in article.

Vladimir Sidorenko