Legacy applications…
… the regular pain in everyone’s back.
Do you have a legacy application application that needs updating or even a rewrite?
The failure of application rewrites often involves a combination of factors, and among these are the following common culprits and factors, being the primary reasons for the failure – not the language it is written in, or the conversion from one language to another or that language not being able to do the work of the other language.
Any turing complete language could do the job, literally, even such a hellish language as brainf*ck..
This shows that the language in itself, is not the problem, and it more often than not comes down to being a case of – what is the right tool to do the job?
I prefer working with Go, as it is a modern language that works equally well on almost any platform, and it is fast to develop and get working results in.
It also has the benefit of being close enough to many other languages, that devs of those, can understand it without any issues.
Also, Go, does typically not really have the inherent issue that many other languages like Python, Java or C++ suffers from with the “legacy library hell”, as it has a modern take on this, with efficient ways of keeping up to date through various mechanisms.
So what IS the big issues then?
Let’s say we start with a classic “problem child” of yesteryears – COBOL, symbolizing the language landscape of more “mature” languages, and using this as an example, as the very same core issues can be applied to pretty much any language.
It really doesn’t matter what the language is, but the underlying problems are commonly the same for all legacy applications.
Lets look at a few key points.
1. Lack of Documentation and Knowledge
- Legacy systems like COBOL often lack detailed, up-to-date documentation. Over time, original developers may leave, and institutional knowledge is lost.
This often enough comes from the claim that code is the documentation in itself. This has never been true, nor will ever be true.
The the code is merely the implementation of the specification and what you did, and never the documentation itself, this specifically as the code itself never describes the original intent of what you set out to do. I will accept documentation to be in the code on the condition that, the comment proceeding the code block explains your Intent and what you plan to achieve, returns etc before you actually write the code. This helps maintainability, as now you can check against this common if it actually does what you said it will do. - Business rules, logic, and workflows are often “baked into the code” without external references, making it hard to replicate functionality correctly.
- Another common example in many Legacy applications is the use of “Magic values”, poorly or undocumented numbers that has specific meanings, But is commonly used throughout the code and will changing such a value can have catastrophic effects. Especially where you do not expect it.
2. Underestimating System Complexity
- These systems have grown organically over decades, often integrating with other systems and processes in undocumented or implicit ways, sometimes using protocols that no longer exists or is poorly documented by themselves, and this is specifically the often the case in the use of proprietary protocols, and even more so when custom hardware is involved, never mind the eternal curse of undocumented storage formats, and especially binary such.
- Dependencies are not always well understood, leading to gaps in the new implementation.
3. Scope Creep and Poor Requirements Gathering
- Stakeholders might not fully articulate all requirements or fail to prioritize them.
- The rewrite team might inadvertently “over-simplify” or “over-engineer” the replacement, causing mismatches with actual needs.
- While there may have been initial documentation, often new additions and rewrites will rarely or never capture the changes in documentation, as many developers still thinks “code is the documentation”.
I have never, even until today, seen documentation being a priority to any greater extent in any commercial Products, with the exception of mission critical systems such as aerospace, oil industry, nuclear, and to some degree medical or similar, and even thenThis is often not by any other means then force of regulation. From what I have seen over my years, so far, anything in the financial industry, looks more like a joke, than anything else.
4. Mismatch Between New and Existing Systems
- COBOL systems often interact with old, niche hardware and protocols that are difficult to replicate or interface with modern platforms.
See my previous point. - Rewrites might inadvertently introduce performance bottlenecks or fail to handle edge cases that the legacy system managed, and again this is often down to poorly understood original requirements and specifications that may not even original requirements and specifications that may not even be available anymore, together with the fact that many developers simply will not together with the fact that many developers simply will not sit down and read such documentation to actually understand what the code does originally.
On a commercial side, there is rarely time allocated for any of this anyway, and you end up paying for it over and over again often to costs exceeding what it would have taken to allocate the time initially, doing it as right as you can, from start
5. Cultural and Organizational Resistance
- Organizations often resist change, especially when it involves mission-critical systems.
- Lack of buy-in from stakeholders or fear of disrupting operations hampers the process.
6. Testing Challenges
- Legacy systems often run for years without interruption, with real-time updates and transactions.
This concept often introduces the fact that you actually have no clear understanding of what’s actually and really running in the machine, especially when hot patches has been applied.
While it can be simple enough for small systems, with bigger systems the complexity often grows exponentially. - Rewriting introduces risks, and testing environments struggle to replicate the production workload, leading to missed issues.
7. Skill Gaps
- Teams tasked with rewriting may lack knowledge of legacy systems and their quirks.
- Similarly, COBOL developers might not be part of the rewrite team, leading to a disconnect between old and new paradigms, including lack of knowledge transfer and especially so for the original intent and meaning of certain things.
8. Cost and Time Overruns
- Rewrites frequently underestimate the effort required, both in terms of budget and time.
This is often down to poor pre-analysis and understanding of the complexity of the task.
A rewrite is almost always more complex than writing a new application from start, because of all the hidden complexities. - Incremental delays add up, and as costs mount, projects are abandoned or deemed infeasible.
9. Failure to Preserve Legacy Business Logic
- Legacy systems encode decades of evolving business logic.
- Translating this logic accurately to new systems without introducing errors is extremely challenging.
- As a consultant on such matters, I often come to the point where the recommendation will be to simply start over, writing the functionality from a clean start, based on the existing perception of, and the requirements for the business logic.
For such projects documentation (specification, documentation, and intent comments in the code) is always a high priority for future maintainability.
Key Point: Lack Of Documentation Amplifies All Other Problems
When documentation is lacking, every other issue is compounded:
- Reverse engineering logic consumes enormous time and resources, and the risk some missing quirks, hidden behaviors etc becomes increasingly large as the by the complexity in size of the application.
- Testing becomes harder because edge cases are unknown.
- Training new developers is significantly more difficult.
Solution Approaches?
- Incremental Modernization:
Instead of a full rewrite, gradually modernize and refactor specific components.
Where possible, break out the individual piece and serve it in a new setting.
One issue at a time. - Automated Code Analysis:
Use tools to extract business logic and system dependencies.
This is one of the things where AI tools can actually make sense, to capture very complex logic behaviors, putting them into simpler words and shortened versions, that’s easier to understand, Giving the developer a head-start understanding of what they are looking at. - Collaborative Teams:
Combine legacy system experts with modern tech specialists.
Not leaving the “legacy teams” behind. They can be absolute key to your success of the rewrite! - Prioritize Documentation:
All documentation should focus on practical maintainability.
Document as much as possible before starting the rewrite and throughout the process.
The documentation and specification is, in the end the benchmark and test specification, to which you measure “are we there yet”?
Do we have the correct and expected behavior?
Also, for the documentation, don’t overdo it.
There is a very valuable balance between detail and general overview.
The specificiations should be the absolute, non-negotiable requirements.
The maintenance documentation, should be practical how-to’s with necessary examples and details.
The protocols and data items should be explained in detail, as this is the basis of all logic.
The data flows should be made clear between the components.NB!!
The code is the explicit implementation – NOT documentation.
I am sorry, but if you claim that the code IS the documentation, you are wrong, for the very reason that anyone can read what you did, but not what you intended to do, and because of this, you should always write “intent documentation” as a block before the actual code, and before you write the actual code. This way, you or anyone else have a fightng chance of correcting mistakes made.
This intent documentation often happens to be the same as the specification, and now, it becomes relatively easy to compare the intent to implementation, to see where the bug is.
Why Modern Languages Like Go?
- Go offers significant advantages for rewriting legacy systems:
- Simplicity: A minimalistic design reduces complexity, making it easier for teams to adopt.
- Concurrency: Built-in support for concurrency enables efficient handling of modern workloads.
- Performance: Go’s compiled nature ensures high performance, rivaling C/C++ in many scenarios.
- Deployment: Go’s single-binary model simplifies deployment processes, especially in cloud-native environments.
Conclusion
Rewriting an application is a significant undertaking, but with careful preparation, stakeholder alignment, and the use of modern tools and languages, it can transform outdated systems into robust, efficient platforms. By addressing risks head-on and employing best practices, organizations can successfully modernize their applications while minimizing disruption and maximizing value.
Do you have legacy applications that needs reworked, modernized or documented?
.. all while using modern tools, technologies, and keeping future maintainability and support in mind?
Let’s talk.