Legacy Coderetreat: Part 5 – Add features on Legacy Code

Add features on legacy code

Blog post series

This blog post is part of a series about legacy coderetreat and legacy code techniques you can apply during your work. Please click to see more sessions about legacy code.

Purpose

When we need to add features in legacy code we need to make sure we understand it. But the feature needs to be added to an existing system. For that we need first of all to understand the current behaviour of the system. After that we need to make room for the change by refactoring the existing code. Of course we need a safety net before refactoring the existing system. Once we have room for the new feature we can alter the existing system. The main purposes of writing tests before adding the feature would be first of all to make sure we do not introduce defects and secondly to make sure we add the wanted feature. By having tests written for the new feature we can validate them with our product colleagues.

During the next section you will find the steps to take in order to minimize the risk of introducing defect or adding the wrong feature to legacy code, what you would do at a legacy coderetreat.

Add features on legacy code

Add features on legacy code

Concept

Below there are some steps you can take in order to minimize the risk when adding a feature to legacy code. These steps respect the Baby Steps concept that minimize the risks of adding defects through small changes that can be understood by our minds and can be remembered easily.

The main things we should care about when adding a feature to legacy code would be:

  • Add the good feature
  • Do not introduce a defect
  • Finish adding the feature in a timely manner

Important: commit after each step to a local source control

Why would you want to commit so often? Well you are working with a system that could be very hard to change and very hard to understand. Mistakes can come out from any small change. You want to have a very easy to use undo button with all the changes. You would not like to be in the situation where some tests stop passing and you do not know what you did. Always when something is wrong, you can undo and continue from a clear state. This is a kind of backtracking in a graph of possible decisions maze.

Steps

  1. Build a Safety Net
    We first need to understand if the described behaviour is a bug or not. For that we can write some characterization tests in order to understand what the system really does. The simplest form of characterization test is a system test. A couple of ideas to start writing the characterization tests are to use the generic approach From Nothing to System Tests or the Golden Master technique. We can generate system tests considering that the System Under Test (SUT) is a black box. You can find more details about how to do that in the blog posts and code casts about the above techniques.
    Do not change the existing code. Just add the tests and let them run as fast as possible.
  2. Add unit or component tests
    We need to add unit tests or component tests around the area that needs to be changed in order to introduce the new feature. This process will show how flexible the current design is.
    With these tests we have a better type of test coverage, because even if we had covered all the code paths with the integrated tests at step 1, we might have forgotten some areas. And it is extremely difficult not to forget something in the process.
    These tests are faster and give us faster feedback than the integrated tests we wrote at step 1.
    Change the existing code just as little as possible so that the unit tests can be written.
  3. Make room for new feature with refactoring
    Now we have two safety nets, one coarse represented by the integrated tests at step 1, and another one which is softer and represented by the unit or component tests at step 2.
    We can use these safety nets to do smart refactoring. Sometimes we just need to improve the names and that’s all. In other cases we need to extract an interface and apply Dependency Inversion Principle. Or we need to do massive refactorings in order to create a more complicated integration layer with the new feature.
    For any integration layer, very simple, basic or very complicated, we need to clearly define the contract of collaboration between the systems. For all the integration layer some tests would be extremely useful and will help during the next step, of adding a feature.
    Careful! Do not refactor too much; you need to refactor the minimum necessary to let the new feature be plugged in.
  4. Add features on legacy code
    Make sure you can add features on legacy code in a timely manner. It is essential for the business to be able to add features on legacy code fast enough so it makes a difference.
    Ideally test-drive the feature. If not at least write tests after you added the feature and make sure you have a good test coverage.
    Run often the unit tests and the tests on the integration layer with the new system. At the end, when the feature was added, make sure to run all the automated tests you have.
  5. Test Manually
    Each time you added a feature, do not rely only on automated tests. At least check the application in a minimal way, or even better, apply a good test plan.
  6. Refactor, clean-up
    Once everything works, we can improve the system with things like:
    – having better names
    – splitting better the responsibilities of the structures
    – making automated tests more expressive
    – refactoring integrated tests into integration tests and unit or component tests
    – a lot of other things…

Remember: commit after each step to a local source control

Outcomes

As with legacy code we do not always understand what is the needed feature or if we do understand, we might not know how we could change the code to accommodate the change, we can use this technique to have more certainty during the process. After this session you will understand how you can add features on legacy code, a code you do now know.

You can use this technique whenever you want to increase your confidence when working with existing code. By Taking Baby Steps you can minimize the risks of introducing defects and maximize your chances to introduce the needed feature.

Remarks

These techniques are not simple to apply. For each of the steps we need to take a lot of care.
For step 2 it always depends on the context if one can add unit tests or component tests.
For step 3 it is always risky to not introduce defects, even if you have some safety nets put in place. Always do small steps and use the IDE for automated refactorings.
For step 4 the main difficulty is to add features on legacy code fast and to test-drive it. We want to increase the test coverage of a legacy system, and what better way to do that than by covering

History

I use this technique whenever I need to add a feature on existing code. I thought about these steps in order to bring me confidence that I am on the good path and I will not introduce defects.

This session is usually part of my facilitation of a Legacy Coderetreat.

Code Cast

Please find here a code cast in Java about this session

 

Feel free to contact me to find more about how to add features on legacy code.

Image credit: http://pixabay.com/static/uploads/photo/2014/02/05/15/04/game-259109_640.jpg

Subscribe

If you want to receive an email when I write a new article, subscribe here:

Subscribe for new articles

Leave a Reply

Your email address will not be published. Required fields are marked *

Post Navigation