This is part of a new mini-series looking at Legacy software - the term "legacy" is often seen as a positive - yet within computing it is a negative term generally uses to indicate the need to replace hardware or software. In last week's episode, I introduced three methods to address legacy software: In this episode, I take a deeper dive into Evolution
Or listen at:
Published: Wed, 17 Aug 2022 15:50:50 GMT
Hello and welcome back to the Better ROI from Software Development Podcast.
This episode is part of a mini-series on Legacy software - what it is, how it occurs and the various strategies to deal with it.
Over the last few episodes, I've introduced it and I've talked about all software being on a sliding scale rather than an absolute of legacy or not.
I've talked about the impact based on just how much effort the software development industry puts into trying to explain it, using things like technical debt, the broken window theory, the messy campground - the shared warnings that seemingly small problems mount up over time and till the system is no longer viable - leading ultimately to an expensive replacement project for something you've already invested in.
And I've taken a look at some of the causes of how we get that legacy software.
And in the last episode, I looked at three approaches to address legacy software:
In this episode, I want to take a deeper look at the Evolution option.
As I said in last week's episode, of the three options, Evolution is my preference. Now, this is somewhat at odds with the software development industry, which seems to favour revolution - the building of the new shiny system.
But I favour Evolution as I believe it, firstly, greatly reduces risk by using small iterations rather than the big bang associated with Revolution - it gets software development investment to market quicker by releasing little and often - and it utilises as much as possible of our existing investment in the legacy system.
So how do we get started with evolving a legacy system?
Well, my first recommendation is the book "Working Effectively with Legacy Code" by Michael Feathers. It is considered the authoritative source on the subject and definitely worth reviewing as part of any legacy software work.
For me, the key takeaway was to look for ways to eat the elephant, ways to make the legacy system manageable piece by piece.
Michael takes a risk based approach and recommends finding seams within the legacy application that allows parts of it to be worked on independently. Consider this a way of scoping down both the risk and the work that the development team have to undertake.
Say, for example, if we have a large, overgrown field to cultivate by hand, it will be much easier to divide that field up into manageable plots, which can be improved independently one by one.
The task is simply less scary the smaller it gets. Thus eating the elephant one mouthful at a time.
As any separate part of the system can be identified, Michael recommends ensuring that it is adequately protected via tests. While ideally these should be automated, having the tests, even if manual, provides a safety net for any changes we subsequently want to make. Then, of course, we then need to make changes to that part of the system to prove that we have adequately identified the seams and we have enough tests in place to control risk.
These changes should be little and often to build confidence that we have both the seams and the tests correct. Once we have that confidence, we can start to see parts of the system as being less legacy than its siblings and we can then move on to the next part.
This will undoubtedly take time and effort, but provides a level of control over risk and effort that the revolution approach simply cannot manage.
In addition to this approach, I'd also like to talk about two complementary approaches - the "Strangler Fig" pattern and removing unused code.
The Strangler Fig pattern.
Martin Fowler originally wrote about the Strangler Fig pattern as a metaphor for dealing with legacy applications.
The Strangler Fig is the common name for a number of tropical and subtropical plant species that have a common growth habit. The plants spend the first part of their lives without rooting in the ground. Rather, they begin life on a victim tree, sucking up the nutrients from its victim. Eventually, the plant grows large enough to seed into the soil and become self-sustaining. But by this point, the victim tree is likely to have died. Thus, the common name "Strangler Fig".
In his original 2004 article, Martin equates this to work that he's done previously with legacy applications. To reduce risk, he would effectively grow a replacement system around the existing, letting it grow slowly over several years until the original legacy system was strangled and could be removed.
This has led to the software industry referring to this as the "Strangler" pattern. Note that Martin actually commented on the name in the article and feels that the "Strangler Fig" name is more appropriate, as better explains the idea of new life growing while slowly killing off the old.
From a technical perspective, this allows us to slowly replace the responsibilities in our legacy system until such point as all of the old code is no longer used.
Say, for example, we have a legacy system that produces a web page which is made up of a header, a footer and some content. We would start by adding our new Strangler Fig application in front of the legacy application. Our Strangler Fig application would receive the request. It would then simply ask the legacy app for the page and return it to the requester.
This may not seem like much - and if anything, it is actually adding an extra step to getting that page - but by being in the middle of that conversation, our new application can start taking over responsibilities one by one.
So maybe the next iteration has the new application, generate the header, and only ask the legacy system for the footer and the content. The next iteration, then it takes over responsibility of the footer. And the final iteration it takes over responsibility for the content, effectively strangling any responsibility that the legacy system has, at which point the legacy system is no longer required and can be safely removed.
This example is obviously quite trivial, but should give you an idea of how, over time and with successive iterations, you can move responsibility out of the legacy application and into the replacement, all while managing risk by keeping the changes small.
You may be asking at this point, is this not an example of Revolution, the wholesale replacement of an application rather than Evolution?
I would argue no.
While the original article by Martin discussed this pattern in terms of an application. The same approach can be used within the same application - creating separation between new and legacy code.
I'd also suggest even doing it at an application level, it is still Evolution due to the approach taken - that lower risk iterative approach. As I mentioned in the last episode, we shouldn't assume that evolution will not allow us to transition our system over time. There is no rule that says the system in question should remain on the same technical stack or even the same structure over time, just that it's done incrementally reducing the risk that comes with that big bang Revolution approach.
Let's move on to the removal of unused code.
This may seem obvious, but is actually rarely done, especially in large legacy systems, as we struggle to identify what code is unused.
By removing unused code, it helps the developers to see the wood for the trees. It helps with their cognitive load when working on the software product.
So how do we decide what to remove if we don't know if it's use?
A few things can help us here - our source control, finding the seams, and the Strangler Fig pattern.
I introduce source control back in episode 18 and it's the answer to the "what if its still needed concern?".
The beauty of source control is that it provides us a full history of our software product - all the code changes over time - the when, the what, the who, and, with the correct developer discipline, even the why.
Thus, when faced with the argument "we can't delete it, we may need it in the future.", then I refer them back to the source control. Even if we delete it today, we can always find it in the history of the source control and add it back later if we need it down the line.
It's much better for us to delete it from the active software and rely on source control then to keep it in cluttering things up.
If we equate this to when we used to run all our financial accounts on paper, we only kept on hand the files that we needed. Accounts for prior years where generally boxed up and sent to offsite archive. Having them littered around the office would make it difficult to find what we needed now, but we still had the capability to pull them back from our archive if we needed to.
In the same way, source control provides us some confidence that we aren't doing anything that cannot be reversed, thus reducing the risk.
We then need to look at what to potentially remove. This is where our working on finding seams within our software product can help. By identifying an area of the whole code base, we are narrowing the scope - and making smaller always makes the job easier.
We can then use something like the Strangler Fig pattern around that seam, ensuring that all use of that code transitions through our Strangler Fig code. Our new code just acting as a middleman between the caller and the code in question. Once in place, our first action is to simply record how often the code is really used. This can simply log to a file or a database when calls are made. This helps us provide evidence that that piece of code under question really isn't used.
Then after a period of time, if the evidence shows that it isn't used, we can use our Strangler Fig code to stop calling the code in question. I'd also advise getting the Strangler Fig code to raise some form of alert should it actually be called. This then allows us to quickly react to the chance that we miss something in an analysis. Once the Strangler Fig code is dead-ending all the calls to the code in question, and maybe after a reasonable validation period, that code in question can be deleted.
Using this approach greatly limits risk by having multiple checkpoints in place to validate if the code is used. And of course, the ultimate ability to revert the removal by going back to the source control.
In this episode, I wanted to introduce some practical approaches for evolving your legacy software out of its legacy state.
While there are a number of approaches available, I really like to focus on limiting the risk of any changes we make. And we limit those risks by following the same modern software development practices that I've advocated throughout the entirety of The Better ROI from Software Development podcast series:
You may be thinking "this seems like a lot of work to fix a legacy system, surely we will be better off replacing it?".
To this, I would ask the question - if these standard modern development practices are not part of your standard development, then how do you think you're going to be able to build a viable replacement?
Without having the sound software development engineering principles baked into the team, then you'll simply be repeating the same mistakes.
In next week's episode, I'll take a look at the next option - Revolution - the wholesale replacement of the system with something new.
Thank you for taking the time to listen to this episode and look forward to speak to you again next week.