Add value to legacy code using agile tools

Shane Auckland
shanethehat
Published in
5 min readMar 2, 2014

--

Working with legacy code need not be the nightmare it is often portrayed as. Tools that are considered best practice for greenfield development can and should be used to support legacy codebases, and prepare them for new development to begin.

Tools, which tools?

Story Behaviour Driven Development (StoryBDD) is a development methodology that allows developers to ensure that they are delivering value with the features that they create by defining the behaviours of an application that the customer finds most valuable. Tools such as Behat allow the human readable rules (Scenarios) agreed by the business analysts to be executed as code, providing all the project stakeholders with an easily measurable gauge of the project’s progress, and therefore its delivered value. Over time as requirements change and new ones are revealed, executing the existing Scenarios as code shows that none of the original value has been lost along the way.

Spec Behaviour Driven Development (SpecBDD) allows developers to ensure that their objects will perform their required function by defining a set of criteria that an object must fulfil. In practise this doesn’t differ hugely from the process of Test Driven Development (TDD), but the focus is on writing with a view of what the code should do, not what it already does. It’s a subtle difference of language that in my opinion makes specs much more readable, especially when used to define behaviour that doesn’t exist yet.

The BDD methodologies are extremely powerful for developing new software; but their ethos, and the tools that drive them, can improve the experience of working with legacy code. If the next guy to maintain the codebase really is a violent psychopath who knows where I live, I’m going to make sure that I leave any project I work on cleaner, better documented and more resilient to change.

The sinking feeling

Most developers have at some point found themselves starting a new job, or joining a new team, and encountered an untested, monolithic legacy codebase that is going to take up the next portion of their professional life. This is a daunting time, fraught with fear of breaking something in some horrifying way that costs someone money. Convincing the customer or manager of the value of a period of exploratory investigation that could last days or weeks is often tricky.

One way that I like to add value to such an exercise is to generate something more than knowledge. Spend time with the customer to understand which parts of their application are most important to them. Establish Behat scenarios for the most valuable aspects of the application first, and then work down until all the visible features of the application are covered with executing scenarios. At a code level, write specs for the existing code base, which will make you carefully examine what the code is doing and what its intention is.

During this process there will undoubtedly be many horrifying discoveries that you can document, and that can later form the beginnings of a backlog of work. A backlog of work that you could not have safely attempted without developing suites of feature and specification tests.

The greatest barrier to writing and kind of tests for existing code is that it may not be testable, and changing to code to make it testable risks breaking functionality that you have not yet identified. This risk should be largely mitigated by your Behat scenarios, which will show if any of the points of value the customer has identified have been broken. If you broke something that the customer doesn’t get value from it’s still not great, but it is significantly less of a disaster.

Specs or tests

In a recent work conversation, I heard someone suggest that specs should only be used for creating new code, and that unit tests written with xUnit tools. I have a few reasons why I think it’s better to use specs to support your existing codebase as well as the code you’ll write in the future:

Don’t make yourself have to run multiple tools.

Having to run multiple tools delays the feedback loop the is fundamental to both BDD and TDD. A shorter feedback loop increases productivity.

Lower technical debt.

Once you have finished covering your legacy code and moved on to writing new features, your xUnit tests would become legacy code themselves. As new design discoveries force changes in the old code the tests will have to adapt, requiring the developers to keep track of two testing tools.

Splitting your tests into multiple tools dilutes your documentation.

I am a great believer in self-documenting code. That’s not to say I think code should be beautifully written prose that describes itself without the need for documentation. In the majority of code I have seen written by excellent developers, nothing is clearer than a single sentence in a docblock that says what a method does. What I mean by self documenting code is things like putting all your route definitions in one file, so when I open routes.yml I know what’s going on without having to go sniffing around the code. Or a clean domain model, so that a glance at the directory structure speaks volumes about the object interactions. And having all your object specifications in one place, so I know that this one file FooSpec.php tells me all I need to know about the expected behaviour of Foo, without having to go looking elsewhere for FooTest.php.

Let’s be realistic

Of course this is all very naive. In reality getting sign off on a long period of writing specifications is not going to be seen as cost effective, and the chances are that you will handle your legacy code with a compromise of some of the above, or possibly even by not covering your old code at all. In an ideal world though, I would like all legacy codebases to be fully covered before any development begins. At a minimum I think that a suite of regression tests should be created as Behat stories so that deviations in important behaviour can be detected, even if the old code is not covered with specs.

Don’t think of this as retrofitting tests. Try not to think about the word test at all (regardless of how many times I’ve used it). You’re developing Stories and Specifications, and whilst it is unquestionably better (in my opinion) to do these things up front that’s not the only way to do it. As long as you’re covering your code, you’re adding value.

--

--