Lava layers and sweeping changes
Every successful piece of software needs maintenance. This maintenance consists of fixing bugs and adding features, but also upgrading internal parts or dependencies. For example, newer versions of dependencies become available and you want to use those. Or it may turn out that some dependency must be exchanged for another.
Most of this maintenance is easy to do as it consists of small changes. But every now and then a new feature request comes by for which more work is needed. The program’s database interaction layer was written by hand initially, but each change makes this more painful. The database layer code grows more and more complex. You wish you had used a good database mapper. The new feature gives you this opportunity to at least make a step towards a database mapper. You spend a few hours selecting one that looks promising, and integrate it. However, you don’t have time to convert all existing mapping code, so can only use it for new mappings. Next time, you promise yourself, you’ll convert the remainder.
How lava layers come into existence
The above example describes the birth of a new lava layer. A new technology is introduced alongside an existing one with largely the same functionality. As cleaning up the old technology takes too much time or is considered too risky, time is spent on keeping both instead. It is not unimaginable that, on a given moment, the second technology also does not suffice anymore and a third one is introduced.
Lava layers are not only applicable to the technologies in use. It may also happen when design choices are changing. I’ve seen occasions where the same responsibility was implemented twice: in an old way and a new way. Half the code base was converted, the other half still in the old way. Both implementations were incompatible, making finishing the conversion a very hard job.
The consequences are clear: supporting more technologies increases the effort required to maintain the code base. Complexity increases, and this scares developers away from making the more invasive changes, increasing complexity even more. In the end, the code is full of lava layers and you find yourself spending lots of time on keeping them compatible.
Having lava layers is a costly business.
How to avoid lava layers
The previous link already has a wise advice:
If you find yourself suggesting a radical change to an existing application, especially if you use the argument that, “we will refactor it to the new pattern over time.” Consider that you may never complete that refactoring, and think about what the application will look like with two different ways of doing the same thing.
Mike Hadlow, The Lava Layer Anti-Pattern
This consideration will help a lot in preventing such lava layers.
I think it also helps to adopt a different thinking pattern: that of the sweeping changes. If you make an improvement like a new technology or a refactoring to a new design, then accept only a few options: either do the complete conversion at once, or, if that would take too much effort, do a step towards making a future conversion possible but do this over the complete code base.
Suppose you see the need to use a dependency injection framework, but your code base is not yet ready for it, for example because there is still too much coupling. There is not enough time to convert the complete code base, and you don’t want to accept lava layers, so you’re also not going to do a partial introduction. What you can do as a first step is to perform the refactoring steps to make the code ready for the transition. This will add value because of the reduced code coupling, but still keep the code in a consistent state. Later on, you can make the actual transition to the dependency injection framework with lower cost.
Starting or finishing?
One of the important lessons here is that it is not the starting, but the finishing that counts. Introducing a technology in the lava layer way may seem valuable, but it will cost a lot in terms of maintenance. Only when the code gets rid of the old technology, you can earn the full value of the new technology.
Making sweeping changes reduces the scope of change so that the change itself costs less time.
It is not the starting, but the finishing that counts.