Wat ik eerst doe als ik een legacy Rails codebase overneem
Drie weken geleden stapte ik een codebase binnen die door vier verschillende bureaus over zeven jaar was opgebouwd. De startup had twee CTO’s versleten, had nooit een tech lead aangesteld en draaide een e-commerceplatform met twee miljoen euro omzet per maand op infrastructuur die niemand volledig begreep. De oprichter noemde het “een beetje ruw aan de randen.” Hij was mild.
Het User-model was 1.400 regels lang. Er waren 23 actieve Sidekiq-queues, drie waarvan niemand kon verklaren. Een migratie uit 2021 had een kolom aangemaakt met de naam temp_status. Die kolom werd op veertien plaatsen gelezen.
Dit is waarvoor ik binnengehaald word.
Na negentien jaar Rails heb ik genoeg van dit soort codebases overgenomen om een systeem te hebben ontwikkeld. Geen draaiboek dat ik mechanisch volg, maar een reeks vragen in een specifieke volgorde die me vertelt waar de echte problemen zitten — en, cruciaal, welke er echt toe doen.
De Gemfile is het eerste hoofdstuk
Voordat ik ook maar één regel applicatiecode bekijk, draai ik:
bundle outdated
bundle exec ruby -v
De Gemfile is een historisch document. Het laat zien uit welk tijdperk de app stamt, welke beslissingen onder druk zijn genomen en nooit herzien, en of het team ooit in routineonderhoud heeft geïnvesteerd.
Een Rails 6.1-app met gems die voor het laatst in 2022 zijn bijgewerkt, is niet alleen technisch verouderd. Het vertelt je dat niemand routineonderhoud heeft gedaan. Beveiligingspatches zijn overgeslagen. Het upgradepad groeit bij elke gemiste versie.
Let specifiek op:
Gems met een vastgepinde versie zonder toelichting waarom. gem 'rails', '6.0.4' zonder context is een waarschuwingssignaal. Iemand heeft hem vastgepind om een reden die ze niet hebben gedocumenteerd. Die reden is nu jouw probleem.
Verlaten gems. Controleer de GitHub-repo voor alles in je Gemfile dat kerninfrastructuur aanraakt. Als de laatste commit drie jaar geleden was met 200 openstaande issues, draag je een afhankelijkheid mee die je vroeg of laat in de steek laat.
De authenticatiegem. Devise is overal. Dat is prima. Maar ik ben codebases binnengelopen die nog steeds de clearance-gem draaiden op een versie uit 2019, of custom authenticatie die tijdens een crisis zonder tests is geschreven. Je auth-stack verdient dertig minuten zorgvuldige lezing voordat je iets anders doet.
Het schema is archeologie
wc -l db/schema.rb
Het aantal regels in schema.rb is geen kwaliteitsmaat, maar de inhoud van het schema vertelt je alles over de geschiedenis van het datamodel.
Wat ik meteen zoek:
temp_- of old_-kolommen die nog in productie zijn. In zeven jaar Rails-apps auditen heb ik er in bijna elke langlopende codebase minstens één gevonden. Dit zijn kolommen die tijdens een crisis zijn toegevoegd als tijdelijke noodoplossing en nooit opgeruimd. Ze zijn vaak load-bearing op manieren die niemand heeft gepland.
Tabellen met meer dan 60 kolommen. Dit is een God table. Hij begon als een schoon model en absorbeerde in de loop van de tijd verantwoordelijkheden omdat het makkelijker was een kolom toe te voegen dan het probleem goed te modelleren. Een users-tabel met 80 kolommen heeft doorgaans de helft van die kolommen die eigenlijk bij drie of vier verschillende domeinen horen.
Indexen — of het ontbreken ervan. schema.rb laat je precies zien wat geïndexeerd is en wat niet. Een foreign_key_id-kolom zonder index op een tabel met miljoenen rijen is een actief productieprobleem dat je op dag één kunt aanwijzen.
Polymorfische associaties. Ze zijn niet inherent verkeerd, maar ze signaleren complexiteit. Elk imageable_type/imageable_id-paar betekent dat je een generieke associatie hebt die mogelijk rommelige domeinlogica verbergt.
Vind de God objects
Elke langlopende Rails-app heeft ze. Models die zijn gegroeid omdat het handig was om logica daar neer te zetten. Ik draai:
find app/models -name "*.rb" | xargs wc -l | sort -rn | head -10
find app/controllers -name "*.rb" | xargs wc -l | sort -rn | head -10
De bovenkant van die lijst is waar ik als eerste tijd aan besteed. Een User-model van 1.400 regels is niet alleen een code smell. Het is een coördinatieprobleem. Elke ontwikkelaar die dat bestand aanraakt, riskeert conflicten. Elke wijziging vereist te veel context. Het wordt load-bearing op manieren die niemand heeft bedoeld.
Wat ik zoek in een God model:
Callbacks die andere models aanpassen. Een after_save in User die Order-records aanpast, is een val. Het maakt User bewust van Order-domeinlogica, creëert onzichtbare koppeling en maakt debuggen een hel wanneer orders zich onverwacht gedragen. Het symptoom is vrijwel altijd een bugreport dat begint met “het gebeurt alleen soms.”
# Het soort ding dat je week kan ruïneren
class User < ApplicationRecord
after_save :sync_order_statuses # waarom staat dit hier???
private
def sync_order_statuses
orders.pending.update_all(notified: false) if email_changed?
end
end
Scopes die complexe bedrijfslogica bevatten. Een named scope van acht regels met drie joins en een subquery had een query object moeten zijn. Als het een scope is, wordt het op onvoorspelbare manieren samengesteld met andere scopes en kan niemand er geïsoleerd over redeneren.
Methoden die hier duidelijk niet thuishoren. Een invoice_pdf-methode op User die een PDF rendert is een lek van domeinlogica. Het model is een opslagplaats geworden omdat dat de weg van de minste weerstand was.
Ik ga dit niet meteen refactoren. Maar ik moet weten waar het zit.
De tests vertellen je wat iemand vertrouwde
bundle exec rails test 2>&1 | tail -5
# of
bundle exec rspec --format progress 2>&1 | tail -5
Slaagt de testsuite? Kan ik hem überhaupt draaien? In ongeveer 30% van de overgenomen codebases waarmee ik gewerkt heb, draait de testsuite niet schoon op een verse checkout. Dat is je eerste datapunt, en het is een betekenisvolle.
Daarna coverage:
grep -r "simplecov" Gemfile Gemfile.lock
Als er geen coverage-tool geconfigureerd is, schat ik de coverage door het aantal testbestanden te vergelijken met het aantal applicatiebestanden. Niet precies, maar “er zijn 15 testbestanden voor 80 model-, service- en controllerbestanden” vertelt je iets reëels en direct bruikbaars.
De tests onthullen ook welke delen van de codebase het team vertrouwde versus welke ze vreesden. Zware testcoverage op betalingsverwerking en lichte coverage op notificaties — dat is een team dat hun risicoprofiel begreep. Nergens coverage — dat is een team dat altijd rende.
De deployment-pipeline is een cultuursignaal
Hoe komt code in productie?
Als het antwoord is “we SSH’en naar de server en draaien git pull,” weet ik al veel over deze organisatie zonder nog een vraag te stellen. Het vertelt me over risicobereidheid, of deployments gevreesde gebeurtenissen zijn, en wat het rollback-verhaal is wanneer er vrijdagavond om zes uur iets misgaat.
Wat ik wil zien:
- Geautomatiseerde tests bij elke pull request
- Een branch-to-deploy-workflow waarbij main automatisch deployt
- Zero-downtime deployment — Kamal, Heroku, Fly.io of equivalent
- Een manier om terug te rollen zonder handmatige database-interventie
Die laatste is de test die de meeste teams niet halen. Als terugdraaien betekent dat je moet SSH’en naar productie om handmatig een migratie terug te draaien, heb je geen deployment-pipeline. Je hebt een deployment-ceremonie.
Een eerlijke beoordeling schrijven
Dit is waar een fractional CTO afwijkt van een consultant die klanten vertelt wat ze willen horen.
Na een week onderzoek schrijf ik een document met deze structuur:
Wat goed werkt. Begin hier altijd, en wees oprecht. Elke codebase heeft delen die het team goed heeft gedaan. Dit identificeren is belangrijk omdat het je vertelt welke patronen je moet versterken, en het toont respect voor de mensen die dit onder echte beperkingen hebben gebouwd.
Kritieke risico’s. Dingen die gegevensverlies, een beveiligingslek of langdurige uitval kunnen veroorzaken. Dit gaat altijd eerst, ongeacht de benodigde inspanning. Een hardgecodeerde API-sleutel in de codebase — ja, ik heb ze gevonden — is een dag werk dat het bedrijf beschermt. Het gaat bovenaan de lijst.
Prestatieproblemen met meetbare bedrijfsimpact. Trage queries op drukke endpoints. Geheugengroei die periodieke procesrestarts veroorzaakt. Deze hebben meetbare kosten en hebben doorgaans meetbare oplossingen. Ze zijn makkelijker te prioriteren omdat je het getal kunt laten zien.
Technische schuld gerangschikt naar hoeveel die het huidige werk vertraagt. Niet alles wat kapot is, hoeft gerepareerd te worden. Een God model in een deel van het systeem dat al twee jaar niet is veranderd, heeft lagere prioriteit dan een God model midden in actieve productontwikkeling. Rangschik schuld naar frictiepijn, niet naar esthetisch vergrijp.
Wat ik niet ga repareren. De moeilijkste paragraaf om te schrijven. Elke codebase heeft legacy-patronen die technisch verkeerd zijn maar functioneel stabiel. Het herschrijven van het polymorfische notificatiesysteem is misschien de juiste keuze over zes maanden. Het is niet de juiste keuze deze week als er een roadmap te realiseren valt. Zeg dit expliciet — als je het niet doet, vragen stakeholders zich af waarom het niet in het plan staat.
De eerste drie fixes
Na de beoordeling komt prioritering. In zeven jaar van dit werk zijn de eerste fixes bijna altijd dezelfde drie dingen:
Voeg error tracking toe als het ontbreekt. Honeybadger, Sentry of Rollbar — kies één. Je kunt niet repareren wat je niet kunt zien. Dit kost een middag en verandert meteen de gesprekken die je kunt voeren over productiegedrag. Voor error tracking begint elk incident met “we krijgen meldingen dat er iets kapot is.” Erna begint het met een stack trace.
Stel een deployment-pipeline in als die er niet is. Zelfs een minimale GitHub Actions-workflow die de testsuite draait en deployt naar staging bij merge naar main is een enorme stap in teamvertrouwen. Het hoeft niet geavanceerd te zijn. Het moet bestaan. Op het moment dat deployments geen handmatige gebeurtenissen meer zijn, begint het team kleinere, frequentere wijzigingen te maken — en zo verklein je in werkelijkheid het risico.
Schrijf op wat je weet. Een docs/architecture.md die uitlegt wat de belangrijkste componenten doen, wat de sleuteldatastromen zijn en waar de bekende mijnen liggen. Dit is geen glamoureuze klus. Het is het werk dat alles mogelijk maakt. Over zes maanden ben je blij dat het bestaat. De volgende ingenieur die moet onboarden ook.
De codebase met 23 Sidekiq-queues, een User-model van 1.400 regels en drie kolommen die als temp_fix begonnen, is niet hopeloos. Ik heb erger in productie gezien, met echte klanten, die echt geld verdienden. Deze systemen zijn rommelig omdat echte mensen met echte beperkingen ze hebben gebouwd, onder druk, met onvolledige informatie. De taak is niet om te oordelen hoe het hier gekomen is. De taak is om het helder te begrijpen, dat begrip eerlijk te communiceren, en het dan stap voor stap beter te maken totdat het risico daalt naar een niveau dat hanteerbaar is.
Veelgestelde vragen
Hoe lang duurt een goede codebase-audit?
Een oppervlakkige audit — genoeg voor een zinvolle schriftelijke beoordeling — duurt drie tot vijf dagen. Een diepe audit van een complexe applicatie, inclusief het traceren van kritieke codepaden en prestatieanalyse op productieomvang, duurt één tot twee weken. Het verschil is belangrijk wanneer je een opdracht met een klant inschat.
Moet ik herschrijven of refactoren?
Bijna altijd refactoren. De “grote herschrijving” is een van de meest betrouwbaar slechte ideeën in software-engineering. Je besteedt maanden aan het herbouwen van functionaliteit die al bestaat, zonder de institutionele kennis van waarom de originele code werkte zoals hij werkte, en je levert vaak iets op met nieuwe bugs die het originele systeem had geleerd te omzeilen. De uitzondering is wanneer het originele systeem zo fundamenteel niet aansluit bij huidige behoeften dat er niets de moeite waard is om te bewaren. Dit is zeldzamer dan teams denken.
Hoe ga ik om met een team dat defensief is over de bestaande code?
Erken de beperkingen waaronder ze werkten. Het User-model van 1.400 regels is niet ontstaan omdat het team slordig was — het is ontstaan omdat ze snel bewogen met beperkte middelen en lokaal rationele beslissingen namen. Benoem dat expliciet. Richt de conversatie dan op wat de code vooruit nodig heeft, niet op hoe het hier gekomen is. Mensen verdedigen code die beoordeeld wordt. Ze werken samen aan code die begrepen wordt.
Een Rails-codebase overgenomen en weet je niet waar je moet beginnen? TTB Software doet technische due diligence en legacy Rails-beoordelingen. Na negentien jaar in dit vak hebben we de meeste situaties die je nu doormaakt gezien — en er weer uit gegraven.
About the Author
Roger Heykoop is een senior Ruby on Rails ontwikkelaar met 19+ jaar Rails ervaring en 35+ jaar ervaring in softwareontwikkeling. Hij is gespecialiseerd in Rails modernisering, performance optimalisatie, en AI-ondersteunde ontwikkeling.
Get in TouchRelated Articles
Need Expert Rails Development?
Let's discuss how we can help you build or modernize your Rails application with 19+ years of expertise
Schedule a Free Consultation