Legacy Code Refactoren met AI: Praktijkresultaten

AI-tools beloven dat ze verouderde codebases kunnen moderniseren. Hoe pakt dat uit bij echte legacy .NET en Java projecten? Resultaten uit de praktijk.

Jean-Pierre Broeders

Freelance DevOps Engineer

10 maart 20266 min. leestijd

Legacy Code Refactoren met AI: Praktijkresultaten

Iedereen kent het scenario. Een codebase van acht jaar oud, oorspronkelijk gebouwd op .NET Framework 4.6, met synchrone database calls, handmatig dependency management, en een Global.asax die meer verantwoordelijkheden heeft dan een CTO bij een startup. Het moet naar .NET 8. Handmatig refactoren kost maanden. Kan AI dat traject versnellen?

Het juiste verwachtingspatroon

Laten we meteen de illusie doorprikken: geen enkele AI-tool gaat een complete legacy applicatie in één keer omtoveren naar modern, clean code. Dat gebeurt niet. Wat wél werkt is gerichte, file-voor-file refactoring waar de AI patronen herkent en omzet.

De truc zit 'm in het opdelen van het werk. Niet "refactor deze hele applicatie", maar "zet deze synchrone repository om naar async/await" of "vervang dit handmatige mapping-blok door een AutoMapper profiel". Kleine, afgebakende opdrachten.

Synchrone code naar async: waar AI uitblinkt

Dit is misschien het meest dankbare refactoring-scenario. Synchrone database calls omzetten naar async is grotendeels mechanisch werk — precies het type patroonherkenning waar AI goed in is.

Neem een typische legacy repository methode:

public List<Order> GetOrdersByCustomer(int customerId)
{
    using (var context = new OrderContext())
    {
        return context.Orders
            .Where(o => o.CustomerId == customerId)
            .Include(o => o.OrderLines)
            .ToList();
    }
}

De AI-gegenereerde async variant:

public async Task<List<Order>> GetOrdersByCustomerAsync(int customerId)
{
    await using var context = new OrderContext();
    return await context.Orders
        .Where(o => o.CustomerId == customerId)
        .Include(o => o.OrderLines)
        .ToListAsync();
}

Keurig. De using statement wordt await using, ToList() wordt ToListAsync(), en de method signature klopt. Bij 90% van dit soort gevallen is het resultaat direct bruikbaar.

Maar — en daar zit de adder — de AI past niet automatisch alle aanroepende code aan. Elke plek die GetOrdersByCustomer aanroept moet nu ook async worden. Dat cascade-effect vereist menselijke beoordeling. Soms zit er een synchrone event handler in de keten die niet zomaar async kan worden.

Configuration migratie: wisselend succes

Het omzetten van web.config XML naar appsettings.json klinkt simpel. Voor standaard connection strings en app settings werkt het prima:

<!-- Oud -->
<connectionStrings>
  <add name="DefaultConnection" 
       connectionString="Server=prod-sql;Database=Orders;Integrated Security=true;" />
</connectionStrings>

Wordt netjes:

{
  "ConnectionStrings": {
    "DefaultConnection": "Server=prod-sql;Database=Orders;Integrated Security=true;"
  }
}

Zodra er custom configuration sections in het spel komen met eigen IConfigurationSectionHandler implementaties, loopt het vast. De AI genereert dan code die compileert maar functioneel afwijkt. Custom XML-structuren worden plat geslagen naar key-value pairs waar de originele hiërarchie verloren gaat.

Waar het écht misgaat: business logic

Legacy applicaties zitten vol impliciete business regels. Een method die CalculateDiscount heet maar ook de voorraad controleert, een logging aanroep doet, en onder bepaalde condities een email stuurt. De AI ziet de code, niet de intentie.

Bij een refactoring van een order-verwerkingsmodule produceerde de AI netjes opgesplitste, SOLID-achtige code. Mooi gestructureerd. Alleen miste het een subtiele null-check die in de oorspronkelijke code als side-effect van een LINQ-query werd afgehandeld. In productie zou dat een NullReferenceException opleveren bij orders zonder bezorgadres — een scenario dat maar bij 3% van de orders voorkomt.

Dat soort bugs vind je niet in een code review. Die vind je in productie, op vrijdagmiddag.

Praktische aanpak die werkt

Na meerdere legacy migraties tekent zich een patroon af dat consistent goede resultaten oplevert:

FaseAI-geschiktHandmatig
Sync → async omzetting✅ 85-90% accuraatCascade-effecten controleren
Config migratie (standaard)✅ Bijna altijd correctCustom sections
Dependency injection setup✅ Goed bij standaard patronenLifetime scoping beoordelen
Business logic refactoring⚠️ Alleen als startpuntAltijd volledige review
Database migratie scripts❌ Te risicovolVolledig handmatig

De gulden regel: gebruik AI voor de saaie, mechanische onderdelen. Sync-naar-async, using-statements moderniseren, namespace reorganisatie. Laat de business logic met rust tot een mens ernaar gekeken heeft.

Testdekking als vangnet

Vóór een AI-gestuurde refactoring moet er testdekking zijn. Klinkt als een open deur, maar bij legacy projecten is die dekking er meestal niet. Het advies: schrijf eerst characterization tests. Niet om te bewijzen dat de code correct is, maar om het huidige gedrag vast te leggen — inclusief de bugs.

[Fact]
public void GetOrdersByCustomer_ReturnsEmptyList_WhenNoOrders()
{
    // Dit test het HUIDIGE gedrag, niet het gewenste gedrag
    var result = _repository.GetOrdersByCustomer(99999);
    Assert.Empty(result);
    // Als de refactored versie hier een exception gooit,
    // weet je dat er iets veranderd is
}

Met die tests op hun plek kan de AI refactoren, en de testsuite vangt regressies op. Zonder die tests is het Russisch roulette.

De tijdsbesparing in cijfers

Bij een recente migratie van een .NET Framework 4.7 applicatie naar .NET 8 (ongeveer 120 bestanden, 15.000 regels code) zag de verdeling er als volgt uit:

  • AI-geassisteerde refactoring: 40% van de bestanden, gemiddeld 70% sneller dan handmatig
  • Handmatige refactoring: 35% van de bestanden (business logic, complexe edge cases)
  • Hybride: 25% waar AI een eerste versie maakte die vervolgens flink aangepast moest worden

Totale tijdsbesparing ten opzichte van volledig handmatig: ruwweg 30-35%. Significant, maar geen wondermiddel. Het verschuift werk van typen naar reviewen. En reviewen van AI-gegenereerde code vereist net zoveel expertise als het zelf schrijven — misschien zelfs meer, want de fouten zijn subtieler.

Conclusie zonder conclusie

Legacy refactoring met AI is geen magie en geen hype. Het is een gereedschap. Goed voor mechanische omzettingen, onbetrouwbaar voor business logic, en altijd afhankelijk van degelijke tests. De grootste winst zit niet in de code die de AI schrijft, maar in de tijd die vrijkomt om na te denken over de stukken die er écht toe doen.

Wil je op de hoogte blijven?

Schrijf je in voor mijn nieuwsbrief of neem contact op voor freelance projecten.

Neem Contact Op