Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Port & Mend over Rewrite

Avatar for Matteo Vaccari Matteo Vaccari
November 13, 2025
22

Port & Mend over Rewrite

Many legacy rewrite projects fall short of expectations, while the old systems still keep the business afloat. A practical alternative is to "port & mend" the legacy code. This involves minimal changes to adapt it to new platforms and technology ("porting") and giving it care through fixes, refactoring and testing ("mending"). The result? Maintainable, deployable and updatable code. While rewrites have their place, porting and mending are often less risky, more affordable and now even more accessible with advances in AI. This talk draws on real-world experience to explore the value and practicality of this approach.

See the presentation in English or in Italian

Avatar for Matteo Vaccari

Matteo Vaccari

November 13, 2025
Tweet

Transcript

  1. © 2025 Thoughtworks | Restricted Port & Mend over Rewrite

    Reducing the cost & risk of dealing with legacy code
  2. © 2025 Thoughtworks | Restricted 3 Sure! Hereʼs the plan:

    You will: • Stop all new development on legacy • Wait for us to be done in 1224 months • On day X we will replace the legacy • And et voilà! It works.
  3. © 2025 Thoughtworks | Restricted 4 Sure! Hereʼs the plan:

    You will: • Stop all new development on legacy • Wait for us to be done in 1224 months • On day X we will replace the legacy • And et voilà! It works.
  4. © 2025 Thoughtworks | Restricted 6 Letʼs rewrite everything! Itʼs

    tempting… But very expensive and risky! Is our legacy code that bad? Could we refactor it and make it whole again?
  5. © 2025 Thoughtworks | Restricted Let’s get things straight about

    legacy 7 • It brought our client to success • It keeps the revenue coming in • It handles 1000s of complex scenarios added over the years • Slow release cycles • Obsolete technology • Buggy and unstable • Only a few people left who understand it
  6. © 2025 Thoughtworks | Restricted About me Extreme Programmer Technical

    Principal Hired 10 years ago in Thoughworks Now keen on AI-assisted SW Development 8 I dealt with legacy modernization for most of my career
  7. © 2025 Thoughtworks | Restricted 1. Two real stories 2.

    Port & Mend? 3. For Developers 4. For non-technical leaders 9
  8. © 2025 Thoughtworks | Restricted The dying VAX 11 Circa

    2005 • A huge steel rolling mill • 20 tonne steel foils to be cooled • A Fortran app running on VAX • A VAX that was crumbling
  9. © 2025 Thoughtworks | Restricted 12 What we did VAX

    Plant PC Water valves Pyrometers The PC would receive the same data as the VAX. A new steel slab would roll every 10 minutes, and we would observe the running Fortran in the debugger It often crashed) Ethernet Disk DB
  10. © 2025 Thoughtworks | Restricted What we did 13 •

    Porting strategy ◦ The core logic was ported to Fortran.Net ◦ The persistency was rewritten in C to use SQL Server ◦ The reporting was rewritten in C# • Testing strategy ◦ Run the two systems in parallel ◦ Compare the outputs
  11. © 2025 Thoughtworks | Restricted What happened? 14 • A

    team of 4 people worked on it from March to November • In November the port was reliably replicating the legacy • So the VAX was finally turned off! We chose you because you promised a port. The other vendors promised a rewrite, and we knew it would take ages to make it work right
  12. © 2025 Thoughtworks | Restricted 16 Is a leading e-learning

    SaaS company Founded in 2005, listed in Nasdaq Main engineering center North of Milano Is helping Docebo modernize parts of their architecture, from July 2024 We ran 5 parallel streams of work, collaborating elbow-to-elbow with Docebians
  13. © 2025 Thoughtworks | Restricted The high-value incremental rewrite 17

    Modular frontend Legacy Rewrite Legacy to microservices µS µS UI UI Delivered incrementally while the system is running Written in Go
  14. © 2025 Thoughtworks | Restricted A less expensive incremental rewrite

    18 Modular frontend Legacy µS µS ACL UI UI The “Anti-Corruption Layerˮ translates the legacy model to the model of the new microservice
  15. © 2025 Thoughtworks | Restricted Pivot on tech approach: safety

    first, remodeling second Proposing a fast-port approach to achieve PHP 7 removal faster 1. Remove the danger of outdated PHP and libraries 2. Now Docebo can decide what is worthy of full remodeling and what is not worth the effort
  16. © 2025 Thoughtworks | Restricted The legacy that “cannot not

    be recovered” 20 Is written with YII v1 (obsolete) in PHP 7 (also obsolete) Relies on obsolete unsupported libraries Some dependencies were copied to the codebase and then customized Large monolithic app • It has “separateˮ apps • But they share a TON of common code • And they share all the dependencies (libraries) The client regarded it as “lostˮ and unsalvageable
  17. © 2025 Thoughtworks | Restricted What we did 21 •

    Start a new workspace with latest PHP and a fresh download of the framework • Copy index.php from legacy • Copy the config files and comment out all libraries initializations • Repeat until you see a working page: ◦ Open index.php in the browser ◦ It complains because of a missing class ◦ Copy the missing class from legacy AND/OR uncomment some config lines The key insight: make it a separate container, that can be deployed independently
  18. © 2025 Thoughtworks | Restricted What happened 1 stream out

    of 4 worked this way It took a lot of sweat and patience initially We created microlibraries to share common code We ported 5 apps this way • Well tested • Updated dependencies Delivered faster then expected 22
  19. © 2025 Thoughtworks | Restricted Modernization options 23 High value

    Less expensive In my opinion, being able to choose based on a good balance between investment cost and business value is very important. So, it remains a valid option if you have areas of low value that you need to bring in, separate, and re-document. Because we also have the advantage that they are now independent services and no longer a monolith Fabio Pirovano, Docebo CTO New microservice with brand new DB New microservice with ACL Port & Mend
  20. © 2025 Thoughtworks | Restricted 25 Lessons learned 1. “Irrecoverableˮ

    code can be recovered 2. “Obsoleteˮ technologies can be worked with 3. Porting is safer and cheaper (where applicable!
  21. © 2025 Thoughtworks | Restricted 26 Kintsugi (/kɪnˈtsuːɡi/, Japanese: 金継ぎ,

    [kʲint ͡ sɯɡʲi], lit. "golden joinery"), is the Japanese art of repairing broken pottery by mending the areas of breakage with urushi lacquer dusted or mixed with powdered gold, silver, or platinum. As a philosophy, it treats breakage and repair as part of the history of an object, rather than something to disguise. We can learn to appreciate the work of the past, warts and all
  22. © 2025 Thoughtworks | Restricted 27 Makers vs. Menders https://corgibytes.com/blog/2015/08/14/makers-vs-menders/

    Makers enjoy initial development & MVPs Menders prefer working on apps that are stable and growing These are flexible preferences not cages!
  23. © 2025 Thoughtworks | Restricted 28 Port and mend example

    strategies In-place improvement Incremental change with guardrails 1. Find ways to test it 2. Apply incremental changes 3. Check you didnʼt break anything Fast, incremental benefits Parallel change Safe sandbox, compare old vs new 1. Copy the source code to new hardware/new container 2. Find ways to test it 3. Improve incrementally until it matches expected output Safely change HW/SW architecture
  24. © 2025 Thoughtworks | Restricted For developers: 30 1. Map

    the territory 2. Make the system safe to operate 3. Avail AI Map Bring safety Change
  25. © 2025 Thoughtworks | Restricted 1. Map the territory 31

    • Enumerate modules • Draw impact graphs • Treat code as data Alessandro Confetti, Code as data, XConf 2022 How many DB tables are there? How big? How many code modules? How big? Which code modules access which tables?
  26. © 2025 Thoughtworks | Restricted 1.1 Draw custom visualizations 32

    Make me a script that draws the package import structure of a Go program as a Dot graph Simon Wardley, Tudor Girba, Rewilding Software Engineering
  27. © 2025 Thoughtworks | Restricted 1.2 Collaborate with the experts

    33 Legacy modernization is a team sport! Engage the original builders
  28. © 2025 Thoughtworks | Restricted 2. Make it safe to

    operate 34 A system should have • Reliable non-regression automated tests • Useful high-level documentation • Useful telemetry from production • A fast local development environment
  29. © 2025 Thoughtworks | Restricted 3. Avail AI Engage the

    AI in conversation! Ask for options not code AI makes Port & Mend even cheaper Discuss refactorings Write tests 35 Explain unfamiliar tech Understand the big picture
  30. © 2025 Thoughtworks | Restricted 3.1 Avail the AI for

    understanding 36 You are tasked to modify a CDK source file. Itʼs 1548 lines of undocumented, uncommented TypeScript. Youʼve never seen CDK before. Now what? > can you document which resources are being created? Based on my analysis of the CDK stack code, here's a comprehensive documentation of all AWS resources being created: ## 🌐 **API Gateway & Networking** ### HTTP API Gateway v2 - **Resource**: `HttpApi` - **Name**: `{stackId}-restApi-LearningRecordStore` - **Features**: CORS-enabled for xAPI compliance, supports GET/HEAD/OPTIONS/POST/PUT methods ### Custom Domain & DNS - **Domain Name**: `{deploy_url}.{base_domain}` - **Certificate**: Retrieved from AWS Certificate Manager via SSM parameter - **Route 53 A Record**: Maps custom domain to API Gateway - **API Stage**: "v1" with auto-deploy enabled ## 🗄 **Database Resources** ### DynamoDB Tables (6 tables total) 1. **Auth Table** (`{stackId}-dyn-lrsAuth`) - Partition Key: `pk` (String), Sort Key: `sk` (String) - TTL enabled, Pay-per-request billing - Production: Point-in-time recovery enabled 2. **DAX Client Table** (`{stackId}-dyn-lrsDax`) - Partition Key: `suiteId` (Number), Sort Key: `installationId` (Number) - Pay-per-request billing
  31. © 2025 Thoughtworks | Restricted 3.2 Avail the AI for

    refactoring 37 > what are good ways to refactor this file to be easier to understand and modify? think deeply about the options! ### Problems Identified: 1. **Monolithic Design**: 1578 lines in a single file with multiple responsibilities 2. **High Coupling**: Database, API, Lambda, and infrastructure concerns mixed together 3. **Code Duplication**: Repetitive Lambda creation patterns and table definitions 4. **Poor Separation of Concerns**: Networking, security, data, and API logic intertwined 5. **Hard to Test**: Tightly coupled components make unit testing difficult 6. **Configuration Scattered**: Magic strings and configuration mixed throughout ## 🎯 **Refactoring Strategy 1: Domain-Driven Modular Architecture** ... ## 🎯 **Refactoring Strategy 2: Factory Pattern for Resource Creation** ... ## 🎯 **Refactoring Strategy 3: Builder Pattern for Complex Resources** ... > please execute refactoring strategy 1 An hour of work with AI gets you from a single 1500 SLOC file to • Documented • Well factored • Multiple single-purpose files
  32. © 2025 Thoughtworks | Restricted Microchess 1976: 1. Documentation 38

    Analyze the MicroChess 6502 assembly code in doc/Microchess6502.txt to understand its architecture and create a detailed plan for porting it to Go. Focus on: 1. Code organization**: Identify the major routines, their responsibilities, and how they interact 2. Data structures**: Document all the page zero variables, their purposes, and how the board representation works 3. Key algorithms**: Understand the move generation, board evaluation, minimax search, check detection, and opening book 4. Flow control**: How the main loop works, state management Output a detailed analysis that will help with porting to Go while preserving the exact chess logic but making it more understandable. The goal is to create a hybrid approach: faithful translation but using Go structs, methods, and conventions to make the logic clearer. Microchess is the first microcomputer chess program, implemented by Peter Jennings in 1976 for the 6502 microprocessor
  33. © 2025 Thoughtworks | Restricted Microchess 1976: 2. Plan 39

    > the goal of this project is to port the Microchess 6502 code to Go. Let's brainstorm how to do it (thinks a lot, proposes a plan that is clearly too big for one chat) > write down the plan to a doc (writes it to doc/PORTING_PLAN.md) > add checkboxes to track progress (it obeys) > can you reimagine the plan so that at every stage, we have something to demo? Demo as in running the WIP program and showing progress? (CRUCIALLY rewrites the plan so that every stage can be tested by human hands)
  34. © 2025 Thoughtworks | Restricted Microchess 1976: 3. Acceptance tests

    40 I spent considerable time iterating with Claude and arrived at this format for acceptance tests name: "Move Knight from black square b1-c3" description: "Tests moving white knight from b1 (01) to c3 (22)" steps: # Execute move: (knight from b1 to c3) - commands: "0122\r" should_continue: true expected_display: |- MicroChess (c) 1996-2005 Peter Jennings, www.benlo.com 00 01 02 03 04 05 06 07 ------------------------- |WR|**|WB|WK|WQ|WB|WN|WR|00 |WP|WP|WP|WP|WP|WP|WP|WP|10 | |**|WN|**| |**| |**|20 |**| |**| |**| |**| |30 | |**| |**| |**| |**|40 |**| |**| |**| |**| |50 |BP|BP|BP|BP|BP|BP|BP|BP|60 |BR|BN|BB|BK|BQ|BB|BN|BR|70 ------------------------- 00 01 02 03 04 05 06 07 FF 01 22 These tests are run against both the legacy and the new program The legacy executes in a Go emulator that was extended by Claude to emulate the serial line
  35. © 2025 Thoughtworks | Restricted Microchess 1976: 4. Detailed analysis

    of algorithms 41 ; CMOVE CALCULATES THE TO SQUARE ; USING SQUARE AND THE MOVE ; TABLE, FLAGS SET AS FOLLOWS: ; N - ILLEGAL MOVE ; V - CAPTURE (LEGAL UNLESS IN CH) ; C - ILLEGAL BECAUSE OF CHECK CMOVE LDA SQUARE ; GET SQUARE LDX MOVEN ; MOVE POINTER CLC ADC MOVEX,X ; MOVE LIST STA SQUARE ; NEW POS'N AND #$88 BNE ILLEGAL ; OFF BOARD LDA SQUARE ; LDX #$20 LOOP DEX ; IS TO BMI NO ; SQUARE CMP BOARD,X ; OCCUPIED? BNE LOOP ; CPX #$10 ; BY SELF? BMI ILLEGAL ; LDA #$7F ; MUST BE CAP! ADC #$01 ; SET V FLAG BVS SPX ; (JMP) ; NO CLV ; NO CAPTURE ; SPX LDA STATE ; SHOULD WE BMI RETL ; DO THE > let's start talking about Phase 5 of @doc/PORTING_PLAN.md . Can you analyze @doc/Microchess6502.txt and render CMOVE in pseudocode? Save it to a file (produces a big doc) > add the description of all the global variables that this function reads or writes (the first shot is usually not good enough; itʼs always a good idea to review it) > /clear I reset the conversation so that Claude is not invested in the work) > Our developer analyzed the CMOVE routine in @doc/Microchess6502.txt and produced this file @doc/CMOVE_PSEUDOCODE.md . Please review it thouroughly for correctness and completeness (it improves the doc)
  36. © 2025 Thoughtworks | Restricted Microchess 1976: 4. Detailed analysis

    of algorithms 42 ; CMOVE CALCULATES THE TO SQUARE ; USING SQUARE AND THE MOVE ; TABLE, FLAGS SET AS FOLLOWS: ; N - ILLEGAL MOVE ; V - CAPTURE (LEGAL UNLESS IN CH) ; C - ILLEGAL BECAUSE OF CHECK CMOVE LDA SQUARE ; GET SQUARE LDX MOVEN ; MOVE POINTER CLC ADC MOVEX,X ; MOVE LIST STA SQUARE ; NEW POS'N AND #$88 BNE ILLEGAL ; OFF BOARD LDA SQUARE ; LDX #$20 LOOP DEX ; IS TO BMI NO ; SQUARE CMP BOARD,X ; OCCUPIED? BNE LOOP ; CPX #$10 ; BY SELF? BMI ILLEGAL ; LDA #$7F ; MUST BE CAP! ADC #$01 ; SET V FLAG BVS SPX ; (JMP) ; NO CLV ; NO CAPTURE ; SPX LDA STATE ; SHOULD WE BMI RETL ; DO THE // CMOVE  Calculate Move and Set Flags // This routine calculates the target square for a move and sets flags to indicate: // N flag Negative): Illegal move (off board or blocked by own piece) // V flag (oVerflow): Capture possible (opponent piece on target square) // C flag Carry Illegal because move leaves king in check // // Returns with A  $00 (legal) or A  $FF (illegal) function CMOVE // Calculate new square position newSquare = SQUARE  MOVEXMOVEN SQUARE = newSquare // Check if off board using 0x88 trick if (newSquare & 0x88 ! 0 goto ILLEGAL // Scan all 32 pieces to see if target square is occupied // Note: BOARD and BK form a continuous 32-byte array in memory $50$6F // The assembly uses BOARD,X with X from 31 down to 0 // X015 accesses BOARD015] at $50$5F (your pieces) // X1631 accesses BK015] at $60$6F (opponent pieces) for pieceIndex from 31 down to 0 if BOARD[pieceIndex] == newSquare: // Square is occupied! // Check if occupied by own piece (indices 015 if pieceIndex < 16 goto ILLEGAL // Blocked by own piece // Must be opponent's piece (indices 1631 // Set V flag to indicate capture using signed overflow trick A  0x7F // Load 127 (maximum positive signed byte) A  A  1 // 0x7F  1  0x80 = 128 in two's complement
  37. © 2025 Thoughtworks | Restricted For non-technical leaders Good reasons

    for a rewrite include: • Architecture does not perform or cannot scale • Encumbered by licenses • Source code lost/cannot build 44 How to choose between Port & Mend or a Rewrite Questionable reasons for a rewrite include: • System is hard to change ◦ But we can change it • System runs on dying hardware ◦ We can port it on new hardware • System is an ugly monolith ◦ We can refactor incrementally Take-home point: be skeptical about tech people pushing for a rewrite Ask for solid evidence
  38. © 2025 Thoughtworks | Restricted What should a non-technical leader

    ask/expect? Ask for • Evidence of safety: “Show me how youʼre proving nothing breaks after each change.ˮ • Incremental value: “What business capability or risk reduction are we delivering in the next 3 months, not just at the end?ˮ • Clarity of strategy: “Which parts will be ported, which will be mended, and why?ˮ • Risk management: “How will we know early if this approach is failing?ˮ Expect to see • Regular visible progress: small, working increments in production. • Parallel verification or strong test evidence: confidence that old and new behave the same. • Improved maintainability over time: system becomes easier and cheaper to change • Business continuity: the legacy keeps running while modernization progresses 45
  39. © 2025 Thoughtworks | Restricted It can be done •

    Port & Mend is cheaper and safer than a rewrite (where applicable! • It requires a mindset shift • It requires careful preparation work • AI makes Port & Mend more convenient Always ask: “What part of this rewrite could we Port & Mend instead?ˮ 47
  40. © 2025 Thoughtworks | Restricted Further readings How AI is

    reshaping IT operations: From firefighting to intelligent evolution AI is transforming IT from system repair to strategic impact. Explore how AIOps drives smarter operations, faster fixes and continuous modernization. AIPowered Developer Upcoming book by Uberto Barbini from the Pragmatic Press Makers and Menders How the two different mindsets click Legacy Code: Big Rewrite or Progressive Rejuvenation? Another author’s perspective on this topic Learning Hours on Legacy Code Emily Bache hosts valuable resources on technical coaching 48
  41. © 2025 Thoughtworks | Restricted 3.4 Avail AI to write

    tests 51 Now let me analyze the getAdminUsers function logic to identify missing test cases. Based on my analysis of the function, here's what it does: 1. Input sanitization: Converts user IDs to integers to prevent SQL injection 2. Complex SQL query: Finds users based on organizational chart membership 3. Filtering logic: a. Returns users with idAdmintMemberOfCount == 0 (admin users from query) b. Also returns users that are NOT found in the query results at all 4. Return value: Array of user IDs that are considered "Admin users" Test Cases Added: 1. testgetAdminUsersWithEmptyArray() - Tests behavior with empty input array 2. testgetAdminUsersWithUserNotInDatabase() - Tests with a single non-existent user ID 3. testgetAdminUsersWithMultipleUsersNotInDatabase() - Tests with multiple non-existent user IDs 4. testgetAdminUsersWithUsersHavingCountZero() - Tests users with idOrgDirectMemberOfCount = 0 5. testgetAdminUsersWithUsersHavingCountGreaterThanZero() - Tests users with count > 0 (should NOT be returned) 6. testgetAdminUsersWithMixedCountUsers() - Tests mix of users with count 0 and count > 0 7. testgetAdminUsersWithMixedExistingAndNonExistingUsers() - Tests combination of existing and non-existing users 8. testgetAdminUsersWithSingleUserHavingHighCount() - Tests user with high count (11) 9. testgetAdminUsersWithDuplicateUserIds() - Tests handling of duplicate user IDs 10. testgetAdminUsersWithStringUserIds() - Tests input sanitization with string IDs 11. testgetAdminUsersWithLargeUserSet() - Tests comprehensive scenario with all user types 12. testgetAdminUsersWithNegativeUserIds() - Tests edge case with negative user IDs 13. testgetAdminUsersWithZeroUserId() - Tests edge case with user ID 0 14. testgetAdminUsersOrderConsistency() - Tests that results are consistent regardless of input order Please extend the getAdminUsers test with any test needed to cover the full logic 💡It generates valuable and less valuable tests Keep the good ones, throw away the others