#Utveckling & Arkitektur, #Helhetsåtagande, #Förvaltning

Återbruk i stället för att köpa nytt

Nyligen skrev jag ett litet LinkedIn-inlägg eftersom jag var så glad att jag var klar och hade fått ihop det. Alltså, fått ihop det här ska handla om. Det nog mest sedda någonsin, med ganska bra marginal, så jag tänkte skriva lite mer om vad som hände.

Det störde mig att fanns en anti-pattern i mitt ärvda system och att den hindrade mig i precis varje förändring jag ville göra av systemet. Eller, som kunde ville att jag gjorde. Den hindrade precis allt. Mitt jobb är att underhålla min kunds system, förnya och förändra efter hand som kundens behov i systemet förändras.

Just den här kunden är också unik, i det att det plötsligt en dag kom följande önskemål:
”Nu har vi rättat snabbt länge, och sagt att vi får städa och gör snyggt Senare. Nu är det Senare, och dags att städa applikationen, så att vi kan förändra den och vidareutveckla!”

Den andra meningen är en jag aldrig hört från en kund tidigare. Den första, däremot, från nästan alla.

Problembeskrivning

Det här angreppssättet är applicerbart på väldigt många problem av den här karaktären, men mitt problem bestod främst i en klassisk anti-pattern av Entity Framework som jag sett många gånger, och, första gången jag själv använde EF, gjort mig skyldig till. Även om det rättades ganska kvickt den gången.

För den som vill läsa mer om just det problem som var grunden för allt det här finns det en ganska vältäckande artikel hos Microsoft själva:

https://learn.microsoft.com/en-us/archive/msdn-magazine/2009/june/anti-patterns-to-avoid-in-entity-framework-n-tier-applications

Det man hade gjort var alltså att man lät sitt EF Context-objekt leva. Länge. Det levde i princip hela användarens session. Det gav, bland annat, prestandaproblem för användaren, men vad det framförallt gjorde när man byggde en web-applikation med ett så långlivat Tillstånd var att det precis omöjligt att ändra i koden. Det blev så fruktansvärt många specialfall av när saker sparades eller inte sparades till databasen, vilka sökresultat som fortfarande var relevanta, var de kom ifrån etc, att varje liten yttepytte-förändring av en funktion kunde skapa ett fel precis var som helst i hela applikationen.

Man hade dessutom låtit EF-implementationen vandra hela vägen upp till presentationen. Vyerna presenterade data direkt ur EF-entiteter, med konsekvensen att varje liten förändring av databasen skulle propagera hela vägen genom stacken upp till vyn. Varje vy. Och varje vy var hårt kopplad mot både databasschemat och tillståndet.

De här två är de två främsta skälen till att en EF-context bör vara oerhört kortlivad. Absolut aldrig bortom ett anrop. För det mesta inte ens det. Överhuvudtaget bör man i möjligaste mån undvika den sortens tillståndsmaskiner när man bygger vanliga web-applikationer. De blir hopplöst komplexa, och det fyller nästan aldrig någon funktion i alla fall. Det är inte första gången jag sett detta, och problemen har nästan alltid varit desamma.

Men, när hela problemet nu är att man inte kan ändra på applikationen, hur kan man då ändra den, så att man löser problemet att den inte går att ändra? Det är ju ett moment 22.

 Konsulten Felix fotograferar

 

Hur börjar man?

Ja, hur gör man? En lösning, som inte sällan föreslås, är att man helt enkelt kastar alltihop och börjar om från början och gör rätt den här gången. Men, det kostar. Detta är ett system som evolverat i 25 år. Hur många förändringar har gjorts? Hur många regler och villkor finns det, egentligen? Jag vet ju hur många specialfall jag träffar på varje gång jag ska göra någon liten pytteförändring. Hur många finns det då inte i hela systemet? Just det här systemet är inte jättestort. Det är ca 50 000 rader C# och html-kod i olika varianter. Men det är ändå mycket om man ska börja om från scratch och kunden är ett tvåmansföretag med en lite begränsad budget. Det skulle kosta flera miljoner att bygga om från början, även om resultatet i slutänden antagligen skulle bli lite flottare, skulle den skillnaden aldrig någonsin komma i närheten av att motivera kostnad och ledtid.

Tricket ligger i att göra lite av båda. Under kontrollerade former. Ungefär som att byta ut sitt kök ett skåp i taget, men behålla luckorna, även om man lackar om dem. Visst kan man ibland behöva karva lite i de kringliggande skåpen, men det är ju fullt möjligt och man har hela tiden ett fullt fungerande kök, ett försumbart byggkaos, och en kostnad som sprids behagligt över tid.

Hur gjorde jag?

Det var ett par saker som behövdes, som inte fanns, som plågsamt nog slog i precis hela applikationen. En av de saker som behövdes var Dependency Injection, som inte fanns. Det lät sig inte göra för en enskild sida. Det blev en stor sak på en gång, men väl värt investeringen. Förändringen slog visserligen överallt, men den gjorde det precis likadant. När den var på plats blev saker mer kontrollerade framöver. Jag ansträngde mig dock för att det inte skulle skena iväg. Små förändringar är bättre än stora i en värld där varje förändring ger upphov till fler fel. Men detta var tyvärr en nödvändig hyfsat stor förändring.

Dessutom var det en nödvändig förändring för att kunna införa vettiga automattester vilket inte fanns. Integrationstester som beskriver systemet ”hela vägen”, i motsats till enhetstester, är Grejen för den här typen av arbete. En stor del av själva syftet med alltihop är ju att flytta runt en massa i koden, och funktioner kommer att hamna någon annanstans, och sannolikt dessutom vara ganska annorlunda strukturerade. Enhetstester som verifierar en viss funktion i en viss liten del av koden blir ju meningslösa när man ersätter dem. I bästa fall. Mer sannolikt är att de blir ett stort hinder när man flyttar runt saker. Integrationstester, däremot, behöver inte bekymra sig mycket för hur saker är implementerade på baksidan, utan låter dig röra runt så mycket du vill i implementationen, och kan sedan berätta om applikationen fungerar likadant som innan.

Detta fick bli steg ett. Och testas noga över hela applikationen. Varje sida. Naturligtvis slutade massor av saker fungera, men det var bara att rätta de felen tills applikationen fungerade som den skulle igen, med DI.

När detta var på plats var det dags att börja förändra. Jag passade på när en viss sida skulle förändras. Passa På är ett ledord i hela den här processen. Jag skapade en helt ny EF-context, i ett eget namespace. Jag skapade ett nytt repo-namespace vars uppgift var att dölja EF för resten av världen, och i stället presentera dto-objekt. Som också blev ett nytt Projekt. Eftersom hela applikationen tidigare använt de klasser som fanns i min gamla EF-context, kopierade jag helt kallt de klasserna till nya dto-projektet, trygg i förvissningen att de skulle ändras efter hand.

Första sidan tog såklart tid. Den behövde förändras så att den använde den nya stacken, med dto-objekt och ny EF Context och nya repository-klasser för att läsa och skriva till databasen. Vissa saker blev riktigt jobbiga, när det visade sig hur mycket till exempel den EF-klass som representerade en Användare användes överallt för till exempel rättighetshantering. Så, nya interface som implementerades av både gamla klasser och nya dto:er. På så vis gick det att skicka även de nya varianterna till den gamla logiken när så behövdes, för att inte allt skulle behöva skrivas om samtidigt.

En lustig sak som visade sig vara ett mönster man kunde se överallt efter hand som sidorna byttes ut var att mängden kod krympte. Och krympte. Den gamla koden innehöll massor av duplicering, och när det skrevs om blev det tydligt, och den dupliceringen kunde tas bort. Alla de kontroller av applikationens tillstånd som funnits tidigare kunde få pensioneras i takt med att varje anrop blev hjälpligt autonomt. I takt med att repository-klasserna växte på bekostnad av de gamla klasserna som läst och skrivit till databasen, fanns det också allt mer färdig kod att använda för varje ny sida jag gav mig på, och det gick lättare och lättare att flytta en gammal sidas till nya stacken. När jag kommit ungefär halvvägs kunde jag dessutom börja plocka bort ansenliga delar av det gamla. De interface som hade behövts för att kunna hantera både nya och gamla varianter i den gamla koden kunde kastas efter hand som även de gemensamma delarna som gällde hela applikationen kunde bytas.

Till slut var det bara borta. De sista delarna kunde slängas, och där fanns en ny, mer konsekvent och tydligare, prydligare applikation.

Lite i taget, ett ”stuprör”, en funktion, i taget, i stället för att försöka byta ut hela lager på bredden. Att försöka slänga ut just den EF-implementation som stört, utan att ändra högre upp i lösning hade aldrig gått. Och det var ju det som var problemet!

Hur gick det sedan?

Sedan detta blev klart har vi kunnat byta hela den grafiska profilen och central funktion för flera delar. Vi har infört en hel rad nya funktioner, med fler på gång.

Resultatet?

Vi har nu en applikation som går att förändra på ett hyfsat säkert sätt. Att lägga till en ny funktion blir verkligen en ny funktion, utan att vi behöver vara oroliga för att saker ska gå sönder på alla de vi har, och det gör allt så mycket mer förutsägbart.

Prestandan är, som en liten bonus, också ofantligt mycket bättre!

Publicerad: 2025-01-28