PROPELOO

LEGACY MODERNISATION / SYSTEM UPGRADE

Modernise the system that is holding your business back — without replacing everything.

PROPELOO engineers legacy software modernisation — from codebase assessment and technical debt quantification through incremental modernisation, framework upgrades, API extraction and the test coverage that makes refactoring safe. Legacy software is not always broken. It is often running critical business logic that nobody fully understands. We modernise it carefully.

Legacy code is not bad code. It is code without tests — which makes it dangerous to change.

Michael Feathers' definition of legacy code is code without tests — code where every change might break something you cannot measure. Most businesses run on this code. The ERP built in 2008, the payment processing module that "just works" and nobody touches, the reporting system written in PHP 5 that generates the board report every month. This code often contains the most valuable business logic in the company — the rules, calculations and workflows that took years to get right. Modernisation is not about rewriting this code because it is old. It is about wrapping it in tests so it can be safely changed, extracting its knowledge into documented business rules, and gradually re-platforming it so it can evolve without the constant fear of breaking something critical.

The legacy modernisation process.

System Layers

  • Assessment Layer: Codebase analysis, technical debt measurement, dependency mapping, modernisation roadmap
  • Test Coverage Layer: Characterisation tests, unit test backfill, integration test suite, regression baseline
  • Refactoring Layer: Safe refactoring under test coverage, code smell elimination, architecture improvement
  • Re-platforming Layer: Framework upgrades, language version updates, database migrations, cloud deployment
  • API Extraction Layer: Business logic extraction to APIs, service boundary definition, strangler fig preparation

Core Technical Capabilities

  • Legacy Assessment

    Static analysis with SonarQube/CodeClimate, cyclomatic complexity measurement, dependency graph analysis, outdated dependency audit, security vulnerability scan and business logic documentation via reverse engineering.

  • Characterisation Tests

    Tests that document what the code currently does — not what it should do. Written before refactoring begins. If a refactoring breaks a characterisation test, the behaviour change is known and can be evaluated. The safety net for modernising code with unknown behaviour.

  • Safe Refactoring

    Extract Method, Extract Class, Introduce Parameter Object, Replace Conditional with Polymorphism — Martin Fowler's catalogue applied under test coverage. Each refactoring changes structure without changing behaviour, verified by characterisation tests.

  • Framework & Runtime Upgrades

    Node.js LTS upgrades, PHP 7→8 migration, Python 2→3 migration, Angular 1→modern, jQuery-era code to React, Java 8→17 LTS — with comprehensive test coverage before starting.

  • Business Logic API Extraction

    Extract critical business logic from legacy monolith into documented, tested REST APIs. The extracted API becomes the canonical implementation. Legacy code becomes a consumer of the API.

  • Database Modernisation

    Schema normalisation, index optimisation, stored procedure extraction to application code, ORM adoption over raw SQL where beneficial, and migration to managed cloud database.

How we think about legacy modernisation.

Working Effectively with Legacy Code (Michael Feathers) is the playbook. The first step is always: add tests. The second step is: refactor under tests. The third step is: never break those tests.

  • Tests before refactoring — always

    Refactoring legacy code without tests is gambling. You make a change, run the application manually, it seems to work, you ship it. Two weeks later a user discovers the edge case that proves it did not work. Characterisation tests document the current behaviour and make every refactoring verifiable. No legacy modernisation engagement starts without establishing test coverage first.

    Axiom:

  • Understand before changing

    Legacy code that looks wrong often contains business rules that are right for reasons nobody documented. The calculation that looks like a bug is compensating for a database quirk introduced in 2012. The conditional that seems redundant is handling a specific customer exception that cost $50K when it was missed. Read the code and the git log before rewriting anything.

    Axiom:

  • Small, reversible changes over large ones

    A refactoring that changes 50 lines in one commit is recoverable if it breaks something. A refactoring that changes 5,000 lines in one commit is a rollback nightmare. Legacy modernisation should proceed in small, independently deployable increments where the production impact of each change is understood and bounded.

    Axiom:

  • Boy Scout Rule: leave code cleaner than you found it

    Full legacy modernisation before new features is rarely justifiable to the business. The pragmatic approach: every time a developer touches a piece of legacy code for any reason (bug fix, new feature), they leave it cleaner — better variable names, extracted function, one more unit test. Aggregate over six months of development and the code quality improves significantly without a dedicated modernisation project.

    Axiom:

Legacy modernisation strategy decisions.

  • Full rewrite vs incremental modernisation?

    Impact: Incremental modernisation or strangler fig for most legacy systems. Full rewrites have a higher failure rate than incremental approaches. "Leave it alone" is the correct answer for stable systems that do not need to change.

    • Full rewrite — clean slate, all risk at end, often fails (The Second System Effect)
    • Incremental modernisation — distributed risk, continuous value delivery
    • Strangler fig — build alongside, migrate incrementally
    • Leave it alone — sometimes the correct answer for stable, low-change systems
  • Test strategy for existing code?

    Impact: Characterisation tests (Golden Master tests) first — document what the code does now, use them as a regression safety net. Add unit tests as code is refactored and business logic becomes testable.

    • Characterisation tests first — document current behaviour, safe to refactor
    • Unit tests from scratch — time-consuming, requires understanding
    • Integration/E2E tests only — coverage without deep refactoring access
    • No tests — not acceptable for modernisation
  • Framework upgrade approach?

    Impact: Incremental framework upgrades with test coverage at each step. Major version upgrades (e.g., Angular 1 → 17) are too large for a single increment — decompose into intermediate steps.

    • Upgrade all at once — highest risk, fastest completion
    • Upgrade incrementally — lower risk, longer timeline
    • New framework alongside old — highest flexibility, highest cost
    • Avoid upgrade — only if system is being replaced
  • Database refactoring?

    Impact: Expand-contract for schema changes: add new columns/tables before removing old ones, migrate application code to use new structure, remove old structure after all consumers migrated.

    • Schema modernisation in place — risky without proper migration strategy
    • Expand-contract pattern — safe, zero-downtime
    • New schema, migrate data — cleanest, highest risk
    • Keep legacy schema, improve queries — lowest risk
  • Documentation approach?

    Impact: Combination: architecture diagrams (system understanding), characterisation tests (behaviour documentation), ADRs for significant decisions made during modernisation, and inline documentation as part of refactoring.

    • Write documentation first — time-consuming before modernisation
    • Write tests as documentation — executable specifications
    • Reverse engineering + architecture diagrams — visual understanding
    • Inline code documentation as you go — pragmatic
  • Prioritisation?

    Impact: Highest change frequency first: code that is modified every sprint benefits most from improved structure and test coverage. Code that has not been touched in 5 years is lower priority.

    • Highest business risk first — address most dangerous technical debt
    • Highest developer friction first — improve team velocity fastest
    • Lowest complexity first — build confidence with early wins
    • Highest change frequency first — most touched code benefits most from improvement

What PROPELOO modernises.

  • PHP Legacy Modernisation

    PHP 5/7 codebase to PHP 8 with modern framework (Laravel/Symfony), characterisation tests, ORM adoption and cloud deployment.

  • jQuery to React Migration

    Legacy jQuery/Bootstrap frontend to React with TypeScript — component extraction, state management, API layer and design system.

  • Java 8 to Modern Java

    Java 8 monolith upgrade to Java 17+ LTS with Spring Boot 3, virtual threads, records and modern APIs — under full test coverage.

  • Python 2 → 3 Migration

    Python 2.7 EOL migration to Python 3.11+ with 2to3 tool, manual fixes, comprehensive test suite and CI enforcement.

  • Stored Procedures to Application Code

    Extract business logic from stored procedures to testable application service layer — with characterisation tests and gradual migration.

  • Legacy API Modernisation

    SOAP/XML API to REST/JSON with backwards-compatible adapter, OpenAPI documentation and modern authentication.

The modernisation toolchain.

  • Analysis

    Stack: SonarQube, CodeClimate, Understand (static analysis), git log analysis

  • Testing

    Stack: Jest / JUnit / pytest, Golden Master testing frameworks, Approval Tests library, Integration test harness

  • Refactoring

    Stack: Language-specific refactoring tools, IDE refactoring support (IntelliJ, VS Code), Custom codemods (jscodeshift)

  • Modernisation

    Stack: 2to3 (Python), rector.php (PHP), angular-upgrade (Angular), OpenRewrite (Java)

  • Database

    Stack: Flyway / Liquibase (migrations), pgloader, AWS DMS, Expand-contract migration scripts

  • Documentation

    Stack: Miro (architecture diagrams), ADR (Architecture Decision Records), Confluence, Code annotations

Legacy systems often have accumulated security debt.

  • Security audit before modernisation

    Assess security posture of legacy system before starting — known CVEs, default credentials, unencrypted data at rest, SQL injection patterns. Address critical security issues first.

  • Dependency vulnerabilities

    Legacy systems accumulate outdated dependencies with known CVEs. Snyk or OWASP Dependency-Check audit all dependencies. Critical CVEs addressed immediately.

  • Authentication modernisation

    Legacy systems often have weak authentication (MD5 passwords, no MFA, long-lived sessions). Modernisation includes authentication hardening.

  • SQL injection elimination

    Legacy code often uses string concatenation for SQL queries. Systematic replacement with parameterised queries as part of refactoring.

  • Secrets in code

    Legacy codebases frequently contain hardcoded credentials. Full codebase scan for secrets, rotation of found credentials, migration to secrets management.

  • Security regression testing

    Security-specific test cases added as part of characterisation testing — SQL injection inputs, XSS vectors, authentication bypass attempts.

From legacy to modern — safely.

  1. 01. Assessment

    Static analysis, technical debt measurement, security audit, dependency audit, modernisation roadmap.

  2. 02. Test Infrastructure

    CI pipeline, test framework setup, characterisation test framework.

  3. 03. Characterisation Tests

    Document current behaviour for highest-risk components via characterisation tests.

  4. 04. Safe Refactoring

    Refactor under test coverage — code structure improvement without behaviour change.

  5. 05. Framework Upgrades

    Runtime/framework version upgrades with test validation at each increment.

  6. 06. API Extraction

    Business logic extracted to documented, tested APIs.

  7. 07. Re-platform

    Cloud deployment, CI/CD, monitoring and handoff.

Frequently Asked Questions

How do you add tests to code that was not designed to be tested?

Characterisation tests (also called Golden Master or Approval tests) work with untestable legacy code — run the code with representative inputs, record the outputs, and write tests that verify the same inputs produce the same outputs. This does not require the code to be well-structured or dependency-injected. It creates a regression baseline. As refactoring improves the code structure, traditional unit tests replace characterisation tests for the refactored components.

When is a full rewrite justified?

A full rewrite is justified when: the legacy system cannot be incrementally improved (no way to add tests, deeply coupled with end-of-life infrastructure), the business logic has been superseded and the new system represents a genuine change in domain model (not just re-implementation), or the legacy system is so poorly documented that understanding it would take longer than rewriting it. Even in these cases, consider strangler fig — build the new system alongside the old, migrate functionality, decommission. Pure big-bang rewrites have a high failure rate.

How do we estimate modernisation cost?

Technical debt can be quantified: SonarQube estimates remediation effort per issue (SQALE model). A 1,000-KLOC codebase with significant debt might show 200 days of estimated remediation effort. Prioritise by: change frequency (how often is each module modified?), business criticality (what breaks if this fails?), security risk (what vulnerabilities exist?). Modernisation cost is a fraction of the full remediation estimate — you only modernise what needs to change.

What is the Boy Scout Rule and how do we apply it?

Leave the campsite cleaner than you found it — applied to code: every time you touch a module for any reason, make it slightly better. Fix one variable name, extract one function, add one test. This compounds over time without requiring a dedicated modernisation project. Applied systematically across a team, the Boy Scout Rule can eliminate the majority of code quality debt within 6-12 months of normal development.