Är gammal kod alltid dålig kod?

Här kommer ytterligare en post om gamla system, gammal kod, ibland slarvigt kallad legacy code (https://en.wikipedia.org/wiki/Legacy_code). Det är intressant att reflektera en stund kring vad som är utmärkande för äldre system eller äldre kod och nedan följer mina tankar i ämnet och vad man kan göra för att känna att man skapar en trygghet i att koden fungerar som den ska just nu och om man i framtiden skulle behöva göra några förändringar i den. Att det också kan kännas lite roligare att sitta med kod som inte känns mossig är såklart något att sträva efter, men det brukar komma med åtgärderna man kan göra för att höja kvalitén.

Bakgrund

Åren som systemutvecklare och konsultande hos olika kunder har gett mig en hel drös erfarenheter av system som i en del fall består av kod skriven för väldigt länge sedan. Inte sällan vill kunden att system ska byggas "mer modernt" för att säkra det så omtyckta systemets överlevnad. Inte sällan ska det moderna systemet lösa exakt samma problem som det gamla systemet, helst på samma sätt och ibland även genom att utföra exakt samma rutin, håll-inne-ESC-samtidigt-som-du-dubbelklickar-MEN-inte-för-snabbt.

Vad hände här egentligen? Man vill skriva en massa ny kod, "modern kod", som gör exakt samma sak som den gamla, man vill behålla sina rutiner, ibland i klass med "The Rosemary stretch" (https://www.instagram.com/p/Bt3FAB4hCxQ). Frågan som man borde ställa sig då, eller egentligen som kunden borde ställa sig, är om man ska skriva ny kod alls. Man kanske ska ta sig en rejäl funderare:

Kan vi höja kvalitén på koden, göra lösningen robust så att den tål förändringar och som gör framtida förvaltning möjlig på ett tryggt sätt?

Det är det här som vi ska dyka ner i nu, höja kvalitén på koden, plocka fram känslan av att koden är bra, det kan finnas brister i den men den är iallafall på väg åt rätt håll. Skriv inte ny kod, den som finns på plats nu kanske håller!

Notera att vi antar att Visual Studio används och språket är C#.

(Kod)sanitet

Begreppet Kodsanitet är nog inte så allmänt vedertaget, jag har faktiskt aldrig hört någon annan använda det utanför dom teamen som jag själv har jobbat i, så här kommer ett försök att förklara min syn på det.

Man kan dela upp kodsanitet i två delar:

  • Cleaning
  • Clone'n'Run

Cleaning/städning

När det gäller städning av lösningar och kod så kommer det in flera parametrar som beror på vem som gör jobbet. Tidigare erfarenheter som man har med sig som utvecklare och vad man har för åsikt eller uppfattning om vad som är städad lösning och ren kod påverkar i stor utsträckning vad slutresultatet blir. Notera också att ju mer man jobbar med koden desto bättre borde den också bli, så det är sannolikt väl värt all investerad tid i det här läget.

Lösningen

Det första som möter utvecklaren när hen öppnar lösningen är dess struktur, ingående projekt etc. Lite mer undangömt är paket-hanteringen (nuget) och vilka ramverk som lösningen bygger på. Detta ger då följande checklista för lösningen:

  • Finns det flera projekt i lösningen?
    • Är det tydligt vad dom ingående projekten har för uppgifter?
    • Finns det kandidater till att bryta isär i flera projekt?
    • Finns det kandidater till att konsolidera utan att sammanslagningen blir för stor?
  • Vad säger nuget-paket-hanteringen?
    • Funkar nuget-restore?
    • Finns alla paket och källor kvar?
    • Kan versioner av paket konsolideras?
  • Viken version på .NET-ramverk används?
    • Är det samma version i alla ingående projekt?
    • Supportas ramverken, minns legacy i inledningen?
    • Är det en stor insats att uppgradera?
    • Brytande förändringar, .NET Core -> 3.1, .NET Framework -> 4.x?
    • Vad skulle en uppgradering ha för effekter på målmiljön?

Bara genom att gå igenom den här checklistan och åtgärda, om arbetsinsatsen inte är för stor, kan göra så att lösningen kan överleva många år till.

Koden

Nu kan det vara dags att titta lite djupare i lösningen, analysera koden och se om det finns saker som kan åtgärdas utan att bryta alltför mycket i den existerande logiken eller flödet i koden. Det finns en uppsjö verktyg för statisk analys av kod. Min favorit är JetBrains Resharper och det kan ha haft effekt på min checklista för koden:

  • Finns det några 'unused using directives'?
    • Ta bort dom, dom gör inte skada nu men kan göra det i framtiden.
  • Finns det några 'using alises'?
    • Ta bort dom, dom gör ofta koden otydligare då t.ex. typers härkomst döljs.
  • Finns det tredje-parts-bibliotek som sköter mappningar, dvs översättning av en datatyp till en annan?
    • Ta bort dom, koden tappar testbarhet och viktigast av allt är att felaktigheter i mappningar kanske upptäcks först i runtime i en målmiljö.
  • Finns det stor klasser?
    • Det kanske är läge att bryta isär dessa till fler mindre klasser för att ta ett steg närmare S:et i S.O.L.I.D.
  • Finns det några tydligt onödiga eller osunda beroenden mellan klasser?
    • Fundera över I och D i S.O.L.I.D. Kan koden struktureras om, utan alltför stort våld på logik och flöden?

OK, vad hände med O och L i S.O.L.I.D då?! Börja med S+I+D så har du kommit en bra bit på väg och koden har med stor sannolikhet fått en höjd kvalité. Jag tror alltså att man kan nöja sig ett tag med enbart S+I+D.

Clone'n'Run

Den här delen är av begreppet kodsanitet ligger i vissa lägen närmare koden än städningen beskriven ovan, men i andra lägen längre från koden. Man kan säga att det ligger närmare ops i begreppet devops (https://en.wikipedia.org/wiki/DevOps), om man väljer att definiera devops lite slarvigt som "först utvecklar man och skriver kod, sen testar man, konfigurerar och releasar till målmiljöer".

I min definition av Clone'n'Run så handlar det om att vilken utvecklare som helst, med en utvecklarmaskin till hands, ska kunna hämta ner den senaste versionen av källkoden, öppna lösningen i Visual Studio och köra igång systemet mha (CTRL+)F5.

Utöver ovan så kan man passa på att titta lite mer på omgivningen, inte bara köra igång systemet utan även vad det finns för kod "runt omkring", vad händer när man vill få ut sina ändringar till målmiljöerna, hur hanteras olika versioner av källkoden etc.:

  • Kan man göra "Clone'n'Run"?
    • Hanteras datalagret på ett kontrollerat sätt, t.ex med hjälp av migrations?
    • Är lösningen beroende av ett centralt datalager eller kan man nyttja t.ex. localdb?
    • Finns det connectionstrings i source control, som innehåller känslig information såsom användarnamn och lösenord?
    • Finns det en beskrivning tillgänglig för hanteringen av datalagret?
  • Finns det ett testprojekt i lösningen?
    • Fungerar alla tester, blir dom "gröna"?
    • Finns det tester som är undantagna?
    • Finns det enhetstester OCH end-to-end-tester?
  • Finns det brancher i källkoden?
    • Är strategin för branchhanteringen dokumenterad eller iallafall namngiven i dom fallen den följer en känd strategi?
    • Är det tydligt vilka versioner som just nu körs i dom olika målmiljöerna?
  • Kan förändrad källkod releaseas till målmiljöerna på ett kontrollerat sätt?
    • Är release-hanteringen automatiserad eller iallafall beskriven?
    • Hur hanteras konfiguration?
    • Finns det känsliga uppgifter i versionshanteringssystemet?

Värt att notera här är att det inte finns några direkta åtgärder beskrivna. Målsättningen med alla punkter ovan är att:

  • Kunna köra systemet på utvecklarmaskin
  • Kunna göra en kodförändring
  • Regressionstesta systemet enkelt
  • Kunna granska förändringen enkelt
  • Integrera förändringen i existerande kodbas
  • Släppa ut förändringen i en miljö för ytterligare testning
  • Få ut ändringen i produktionsmiljön på ett kontrollerat och robust sätt

Det är en hel del av ovanstående som främjar trygghet i samband med förändring. Om man vill eller behöver lämna ett system till någon annan att förvalta så ska denna överlämning vara tydlig och enkel. Att bygga ett system för att lösa en uppgift är oftast inte så svårt, men att få systemet att hålla en tillräckligt hög kvalité för att främja också förvaltningsbarhet kräver en mycket större insats och en hel del hantverkarskicklighet.

Avslutning

Ovan checklistor och frågeställningar är det som jag har samlat på mig genom åren. Listorna har förändrats beroende på uppdrag, typ av system, kund etc. Just nu ser dom ut så här.

Man behöver såklart inte specificera listor och skriva ner dom, det viktiga är att man bygger sina lösningar med en tydlig målsättning och en strävan efter att hålla hög kvalité både för slutanvändare OCH kommande utvecklare. Vem vet, utvecklaren som ska rätta ett fel om två år kanske är du själv!