Dealing with Legacy code

In the software industry, you rarely start a project on a green field. In most cases the project will come with a remarkable amount of legacy code [3]. And as the project grows the software certainly gets scary to touch. So let’s face the challenge with some easy strategies.

Respect

Assume that developers have done their best job at the time. Do not be harsh and do not judge. Just because something is unfamiliar, doesn’t mean it’s totally bad [4].

Baby steps

Working with legacy code is a little like taming a dragon. There are dozens of code chunks that do not follow the conventions or need to be refactored or fixed. Start small! Refactor a small part of the code and then move on to the next one.

And if it ain’t broken, don’t fix it. Don’t try big changes in the beginning [4].

Test, test, test and …. test

If the legacy code is not tested, write meaningful tests before (!) refactoring. You certainly want to ensure that everything is working just as it did before. Don’t forget to write meaningful tests for your new code too. And if there are no tests at all, just write tests for the code chunks you are currently working on (Remember: small steps!).

Break hidden dependencies

Sometimes legacy code is hard to test because it has many hidden dependencies. Dan Limmerick proposes 3 strategies to handle these dependencies in tests [2]:

Test the class itself, with minimal references.

     orderService = new OrderService(null, null, null, dependencyICareAbout)

Use Michael Feather’s Subclass and Override pattern

     public void saveOrder(int orderId){
         Order order = orderRepository.getOrderById(orderId);
         getOrderChanges();
         saveOrderToFile(order);
     }

    public virtual void saveOrderToFile(Order order){
         //File stream stuff
     }
 

It uses inheritance to set all dependencies that are irrelevant for the test to null. Basically it allows overriding functionality in a specific class without harming the existing code. The new specific class (TestOrderService in this example) can then be used for testing.

    public class TestOrderService extends OrderService{
         @Override
         public void saveOrderToFile(Order order){
             //Do nothing
         }
     }
 

Make code testable by opening the class up for testing without changing logic. This basically means refactoring. Think about shortening your methods by extracting parts of it into other methods. Shorten the class by considering the single responsibility principle etc.

Another strategy for breaking hidden dependencies could be using mocks in your test code. However, the best solution would be writing lowly coupled code from the start. As Low Coupling is a sign of readable, well-structured and good designed software.

Understand different programming paradigms

The ability to read several programming languages is a great prerequisite for tackling legacy code [4] . But it is not just about learning different languages. It is about approaching a problem from different angles. This will provide you with the necessary ability to look for the best solution for your code. This applies to legacy code as well as your new code.

Beware of old habits

Refactoring towards understandability is nice and considered as a clean code practice. But it may influence developers not only positively but also negatively.

“So-called ‘clean code’ does not immediately improve one’s understanding of code, if one is used to working with the old structures present in source code. As such, we can say that old programming habits do die hard. We also see our initial observations as a call to arms with regard to investigations that try to shed light on how developers grow attached to a certain coding style, however inefficient, and how difficult it is for developers to change their attitude. “ [2]

But how to break those habits? If you want to change something start with yourself. Are you open-minded? Do you approach a problem from different angles? Are you thinking about “our” code (instead of “my” and “your” code) when you are working in a team? Have a growth mindset. Actions speak louder than words.

Collaboration

Collaborate with your peers. It certainly minimizes the time you might spend on reading incomprehensible code. Discussing the solution may shed light into their approach and you might be able to break old (and bad) habits, by involving them in your refactoring proposal.

Code Conventions

Writing code that follows a specific convention is a clean coding practice. Follow a code convention if one exists. Apply it every time you get in touch with a legacy code and also to your newly written code [4].

Avoid pitfalls right from the start

If you have the possibility to start a project from scratch, remember “legacy code is simply code without tests.” So never forget to write meaningful (!) tests. Use Continuous Delivery to enable building, testing and deploying software faster and more frequently. Even if the project has serious time pressure, take your time to write the code. In the long-term this will help you build working software.

References

[1] D. Limmerick (2012): Breaking Hidden Dependencies in: https://danlimerick.wordpress.com/2012/06/11/breaking-hidden-dependencies/

[2] E. Ammerlaan, W. Veninga, A. Zaidman, Y.-G. Guéhéneuc, B. Adams, A. Serebrenik (2015): “Old Habits Die Hard: Why Refactoring for Understandability Does not Give Immediate Benefits”, SANER, pp. 504-507, 2015

[3] M. Feathers (2004): Working Effectively with Legacy Code, Pearson Education, United States

[4] A. Nicolaou (2014): Eight Strategies For Tackling Legacy Code You Didn’t Write

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s