Patternskolen har nå kommet til tiende del og det er på tide å runde av. Tanken var å omtale ett design pattern i hver del, slik som i forrige del om Fluent interface, men som oftest har det blitt flere. Veldig mange patterns ligner på hverandre, enten i implementasjon eller intensjon, og det var derfor naturlig å sammenligne flere patterns. Jeg tror jeg har beskrevet mange av de vanligste patterns som er i bruk, og da passer det å avslutte med noen av mine “favoritt” anti-patterns.
Vi har for øvrig så vidt vært innom anti-patterns i delen om Singleton vs. Static gateway.
Dette var mitt favoritt-pattern (dvs. jeg visste ikke at det var «anti») i mange, mange år. I dette anti-patternet skiller man ikke mellom GUI-logikk og forretningslogikk. Typisk har man en event handler for en knapp eller lignende og så putter man all koden sin i den. I mitt tilfelle puttet vi alle SQL-setninger rett i event handlerne for knappene i brukergrensesnittet.
Steder man typisk finner dette er applikasjoner skrevet i Windows Forms eller ASP.NET Web Forms, eller enda eldre teknologier som Visual Basic 6.
I dag, når Model View Controller (MVC) og tilsvarende er mer utbredt, er det heldigvis sjelden man ser dette.
Anemiske domenemodeller kan fort misforstås som god objektorientering og Domain Driven Design. Dessverre oppdager man ved nærmere ettersyn at forretningslogikken ikke ligger i disse objektene. Den er i stedet spredt rundt andre steder i koden, i beste fall i ulike “tjenester” som behandler disse tomme objektene (“tomme” fordi de bare har get’ere og set’ere). Det er ingenting galt med DTOer om de blir brukt riktig, men hvis det er de eneste domeneobjektene du har i applikasjonen, er det et dårlig tegn.
Den rake motsetningen til Anemic domain model er God object. Gudeobjekter får du når du stapper for mye kode og for mye ansvar inn i ett objekt, og dermed bryter prinsippet om separation of concerns. Dette anti-patternet implementeres med de beste intensjoner fordi man har hørt at innkapsling er viktig i objektorientering. Derfor innkapsler man alt i samme objekt. Bruk av Active record patternet, omtalt i et tidligere innlegg, kan føre til gudeobjekter.
Her har jeg egentlig ikke syndet så ofte. Da jeg gjorde de største tabbene, og skrev den dårligste koden, brukte vi ikke objektorientering.
Sirkulære referanser (også kalt gjensidig rekursive) får man hvis to eller flere moduler er avhengige av hverandre. For domeneobjekter trenger ikke dette å være et problem. For større moduler er det regnet som et anti-pattern.
Dette er forøvrig ikke støttet av assemblies i .NET. Jeg implementerte en gang en GUI-komponent og gjorde et forsøk på å bruke MVC. Vår fremgangsmåte den gangen var å implementere patternet med én assembly for modellen, én for viewet og én for controlleren. Dette førte til en sirkulær avhengighet mellom de tre assembliene. Den eneste løsningen var å innføre enda flere assemblies for å løse opp i dette. Jeg endte til slutt opp med ni assemblies for den ene komponenten. Som du sikkert skjønner, så endte jeg opp med å forkaste denne løsningen.
En annen klassiker i Visual Basic 6 (før .NET) var binære avhengigheter til andre moduler. På denne måten kunne man opprette sirkulære avhengigheter der kildekoden ikke lot seg kompilere uten binærfilene. Først laget man én modul. Deretter laget man en modul til og refererte til den ferdigkompilerte modulen man lagde først. Når modul 2 var kompilert kunne man gå i modul 1 og legge til en referanse til den kompilerte modul 2. Uten binærfilene (DLLene) var det ingen måte å kompilere kildekoden på.
Sekvensiell kobling er når man krever at metoder i en klasse kalles etter hverandre i en bestemt rekkefølge. Dvs. at man må kalle metode A før man kan kalle metode B. Kaller man metode B uten å kalle metode A først, får man gjerne en exception. Kanskje en NullPointerException eller annen feilmelding som ikke er veldig hjelpsom.
Jeg har sett pipelines bli implementert som én klasse med flere metoder som måtte kalles i en gitt rekkefølge. Det kan illustreres med kalkulatoreksempelet under, som er to varianter av den klassiske kalkulatormetoden “add”:
Good
int add(int a, int b);
BadUgly
void setA(int a);
void setB(int b);
void add();
int getResult();
Magiske tall eller bokstaver er ikke-navngitte konstanter som er brukt flere steder i koden. I beste fall har man et refaktoriseringsproblem hvis man endrer konstanten. Er man ekstra uheldig har konstanten en udokumentert betydning. “42, sier du? 1337; hva betyr det?”
Dette kommer jeg over alt for ofte, og jeg blir like trist hver gang. Dette er feil man ikke burde gjøre i Grunnkurs programmering og likevel ser jeg godt voksne folk som driver på slik.
Alle har vel gjort dette fra tid til annen? Altså “gjenbrukt” kode ved å kopiere den fra ett sted i koden til et annet, i stedet for å refaktorisere den ut i en gjenbrukbar komponent.
I gode, gamle dager (da vi kodet SQL i event handlere i GUI og lagde sirkulære referanser) lagde jeg alltid ny funksjonalitet på denne måten. Kopierte et skjermbilde (en «form») jeg trodde var i god forfatning og gjorde endringer på det inntil det tilfredsstilte kravene til den nye funksjonaliteten. Når vi oppdaget feil i koden vi hadde kopiert måtte/kunne vi gå tilbake til alle/noen andre skjermbilder med samme feil og fikse den der også. Hvis vi hadde tid/husket det.
Dette er to anti-patterns som har både samme implementasjon og samme intensjon, eventuelt at kjært barn har mange navn. Anti-patternet er når man har funnet seg en metode, et rammeverk eller lignende som man bruker til ALT, selv om det finnes bedre alternativer. Her har jeg sett mye rart.
Et godt eksempel er når man nettopp har begynte å lære patterns og bestemmer seg for å (mis)bruke patterns til alt man gjør. Jeg lagde en gang en factory, og den kunne godt være en singleton også. Og så viste det seg at den var avhengig av en annen factory osv. Da jeg hadde tre singleton factories på rad innså jeg at jeg var på ville veier.
Cargo cult programmering er når man rituelt bruker kodestrukturer eller design patterns ukritisk, selv når det ikke er grunn til det. Et eksempel er hva vi gjør i Java (og C#), som f.eks. å navngi konstanter med BARE_STORE_BOKSTAVER fordi de gjorde det slik i C på 70-tallet.
Legg merke til det med design patterns i paragrafen over. Når man først lærer patterns åpner det seg en ny verden. Det er spennende å ta i bruk patterns i alt man gjør, for en føler at koden blir så mye bedre av det. Men ikke bestandig, slik jeg viser i eksempelet med «singleton factory». Derfor vil jeg si at man behersker ikke et pattern før man også vet når man ikke skal bruke det.
Da passer det kanskje å avslutte med at overdreven bruk av design patterns kan føre til over-engineering og at det enkle er ofte det beste, så får heller patterns innføres når man vet de trengs.