lørdag 15. desember 2012 Julekalender Legacy
Jeg har hengt endel rundt på de programmeringsrelaterte forumene på diskusjon.no, og en av dem jeg har lagt merke til der er brukeren GeirGrusom. Han har vært medlem siden 2003, og har i skrivende stund over 12 tusen innlegg. Det verste er at det meste jeg har sett ham skrive faktisk er nyttig og relevant for diskusjonen.
Så jeg sporet opp denne GeirGrusom – eller Henning Moe, som han faktisk viste seg å hete. Og han ville gjerne hjelpe til med årets julekalender.
Hvem er du?
Hobbyutvikler blitt proff for å slippe å jobbe som truckfører.
Hva er jobben din?
Jobber som IT konsulent hos OKB AS.
Hva kan du?
Jeg jobber primært med C# og .NET i backend og er sjeleglad så lenge jeg slipper å jobbe med UI.
Hva liker du best ved yrket ditt?
Det beste med yrket er egentlig å jobbe med noe som faktisk påvirker tusenvis av mennesker hver eneste dag. Jeg får bare håpe påvirkningen er positiv.
De fleste prosjekter har det: En del av kodebasen som ingen vil røre!
Legacy kode og unit-testing av dette er det skrevet bøker om, fordi det er en skitten jobb, og svært få vil faktisk ha noe med det å gjøre. Som regel er dette teknisk gjeld som aldri blir tilbakebetalt fordi man ikke har tid, eller prosjektledelsen ikke har ressurser til å takle det. Koden benytter ofte gamle konsepter som enten er sett på som utdatert, eller som aldri burde vært der i utgangspunktet. Det kan være fra en svunnen æra hvor man benyttet et merkelig programmeringsspråk, eller man trodde at string alltid var den beste måten å behandle data med (såkalt "string typing"). Det kan være svære klasser som benytter seg utelukkende av singletons (som er sett på som et relativt dårlig konsept, men likefullt nyttig i sammenheng som abstract factory), eller fullstendig logikk med kun statiske funksjoner (som er det verste en kan røre i forbindelse med unit-testing) fordi forfatteren kanskje ikke forstod objektorientering når det ble skrevet.
Jeg skal bare fortelle litt om mine erfaringer med legacy kode.
Legacykode kan være skrevet i et gammelt språk, eller i en dialekt som ikke lenger brukes (eksempelvis C# 1.0 selv om prosjektet ellers er skrevet i C# 4.0). For C# sin del betyr dette veldig mye kode med svakt typede komponenter slik som ArrayList eller HashSet mot List<T> og Dictionary<TKey, TValue> og som for C# sin del både betyr nedsatt ytelse, dårlig lesbarhet og vanskelig å vedlikeholde. Ofte blir det bare liggende fordi koden ikke er dekket av tester, og fordi utvikleren da løper en risiko for å ødelegge produksjonskode dersom dette ikke blir dekket av tester før noe som helst annet blir gjort, noe det ikke alltid vil bli satt av tid til.
Det er noen ting å være obs på når man skriver unit-tester: det hender at det er feil i legacy-kode som ikke blir fanget opp. Faktisk er det slik at en må ta en avgjørelse om man skal rette disse, for det hender sluttbrukere har gjort seg vandt til disse problemene, eller at andre deler av systemet har jobbet seg rundt disse.
Det jeg har merket er ganske vanlig er at behandling av lokalisering ofte er gjort feil. Bytt tallsystem eller valuta på maskinen og koden feiler. Dette fordi det er blitt hardkodet inn datoformater eller antar at desimalskilletegn alltid er punktum eller komma. Flere steder har jeg sett at standardinnstilling er satt til USA (av en eller annen grunn), og da er det snikende feil med datoer da USA bruker kanskje verdens mest dustete måte å skrive datoer på.
Sikker trådbehandling er også en tilbakevendende feil med eldre komponenter som ikke blir lagt merke til. Mange utviklere vurderer ikke dette når programmet blir utviklet, og kanskje det som regel ikke er et problem i produksjon, eller oppleves som et fantomfeil som er umulig å gjenskape i test. Det var også noe som er blitt satt mer fokus på i de siste årene da det er langt mer vanlig å ha mange prosessorer på systemene, og det er da logisk å ha et bedre rammeverk for behandling av flere tråder. Bytt ut suspekte komponenter med trygge versjoner. Selv om komponenten fungerer uten å være trygg, så er det liten skade i å være sikker på at den er trygg. Se etter dette i singletons og statiske funksjoner. Singletons som inneholder en state er bortimot garantert å kunne føre til slike feil.
... kan jeg fortelle at jeg nylig fikk en feil i produksjon som jeg forårsaket. Problemet var at jeg rettet en feil i koden som var validering av fakturalinjer. Dette ble gjort med et regulærtuttrykk som sjekket et merkelig karaktersett, men dessverre sjekket om dette gjentok seg 0 eller flere ganger uten noen anchors, eller at den engang sjekket om en match gikk over hele uttrykket. Jeg rettet dette slik at valideringen faktisk gjorde noe, og det kom ganske fort klager fra kunder om at de ikke lenger klarte å fylle inn i fakturaer dersom visse tegn befant seg i dem.
Dette med validering av slikt er noe jeg er litt imot. Aldri utfør valideringer som ikke har noe funksjonelt grunnlag. Jeg kan garantere at det dukker opp sluttbrukere som bruker ´ eller ` istedet for ' eller folk med merkelige tegn i e-posten sin. Ikke utfør valdering for noe som ikke er en teknisk begrensning. Ved siden av å kaste bort tiden din, så vil det føre til flere support-tilfeller. Her har jeg opplevd at de som skriver spesifikasjonene legger inn begrensninger i validering utelukkende fordi de har sett de begrensningene andre steder. I mitt tilfelle var dette at spesifikasjonen hevdet at passord ikke kunne ha mellomrom og kunne ikke være lenger enn 10 tegn. Ved å teste, så viste det seg at dette ikke var tekniske begrensninger.
Det er også ofte en god del overutviklede komponenter i legacy kode. Dette er ofte klasser som en utvikler har skrevet, og beholdt som et lite privatprosjekt der en klasse blir utvidet og utvidet til å takle alt mulig som strengt tatt ikke burde vært nødvendig, kanskje over flere år. Problemet med klassene er ikke at de ikke fungerer, men at dersom kravene til komponenten endres, så er den kanskje så omfattende at det enkleste og billigste er å bare forkaste hele komponenten til fordel for en ny, og enklere, noe som gjør at en komponent som det er brukt mange timer på, bare må kastes fordi ingen lenger har kontroll på hva den gjør, eller hvordan den fungerer. Det jeg har opplevd av dette er en utrolig omfattende querystring klasse, ORM system, og programflytmodeller.
For meg er kode fin når den er simpel, løst bundet, sterkt typet og enkel å bruke.Legacy kode kan ofte ikke tilhøre noen av disse kategoriene, noe som er frustrerende dersom en skal skrive unit-tester av eksisterende kode. En må bare starte fra bunnen og skrive en integrasjonstest, og dermed starte med refaktorering i andre enden.
Tredjepartskomponenter er det første som må løsrives. Disse er verdiløse i en unit-test, og i beste fall suspekte i en integrasjonstest, og kan skape store problemer. Finn et mock-objekt rammeverk. Det er ikke spesielt gunstig om kode inneholder en hel masse override klasser som ikke gjør noe som helst, og enda verre om de inneholder mock-klasser som gjør noe spesielt. Ikke skriv simulatorer, eller lignende. Det er en bjørnetjeneste som noen andre kanskje må rydde opp i senere. Dersom det er enkelt å lage mock-objekter, så lag en som utelukkende returnerer forventet verdi. Dersom denne verdien blir generert av noe annet enn konstanter, så er det i utgangspunktet utestet kode i en unit-test, og det har ikke noe der å gjøre.
... så er det viktig å bare ha i bakhodet når man utvikler at kode må leses og kanskje videreutvikles av andre. Det er ikke farlig å ha mange klasser, bare navn, forståelse og kontekst er enkelt å skjønne. En kan ikke ha for mange interfaces (innenfor rimelighetens grenser), og for enkelt å unit-teste gjør det ingenting om kjernefunksjonalitet i en klasse faktisk er bestemt av interfaces gjennom inversion of control. Eksempelvis kan private funksjoner erstattes av et interface og en privatindre klasse som implementerer dette.
En vil alltid oppleve at kode man har skrevet vil bli legacy, men det er fint å sørge for å minimere problemene knyttet til dette. Få også andre til å gå over koden din, og det burde være noen som ikke er redd for å påpeke slike problemer. Hvis koden din alltid blir godkjent av den andre utvikleren, burde du kanskje finne en annen som er litt mer kritisk til det du gjør.