Enums, switch statements en SOLID - deel 7

Slimmere Enums

Een eeuwigheid geleden - april vorig jaar - schreef ik een reeks blogs over een… - hoe zal ik het zeggen…? - over een visueel gehandicapt stuk code dat ik in het wild tegenkwam. Het ging om wat logica die rondom een Enum gebouwd was. Op basis van een bepaalde Enum-waarde, moest de ene of de andere string geretourneerd worden.

In niet twee, niet drie, niet vier maar in zes (!) blogs refactorde ik dat stuk code aan de hand van de SOLID-principes, totdat ik iets had wat onderhoudbaar én performant was.

Twee doelen

Het doel van die blogs was tweeledig. Ten eerste wilde ik het lelijke stuk code tot presentabele proporties op kunnen schonen. Ten tweede was het een mooie gelegenheid om de SOLID-principes toe te kunnen passen op een afgebakend stukje code.

Wat het tweede doel betreft, kan ik mijn blogs nog steeds als een geslaagde onderneming opvatten. Het is als ontwikkelaar verleidelijk om je te concentreren op nieuwe technieken en features, en de basisvaardigheden te laten versloffen. Alle tijd die je besteedt aan een oefening in het schrijven van schone code is de moeite waard.

Het eerste punt is problematischer. Want hoewel ik uiteindelijk inderdaad uitkwam op onderhoudbare, performante code, introduceerde ik met mijn oplossing ook een hoop complexiteit.

- Is het, bijna een jaar na dato, nog steeds vol te houden dat er een interface, een stuk of wat implementaties daarvan en een singleton (en Reflection als de lijm tussen al die elementen) voor nodig is om een stukje logica aan een Enum te kunnen koppelen?

Stel, je zou het onderstaande stukken code in het wild tegenkomen…:

…zou je dan roepen: “Ah, duidelijk!” Of zou je iets verzuchten over, eh, een visuele handicap? - De vraag stellen is hem beantwoorden, natuurlijk.

Wat wil ik?

Dus: terug naar de tekentafel. Wat probeerde ik nu eigenlijk te bereiken? - Eigenlijk is mijn use case betrekkelijk eenvoudig. Ik wil wat logica kunnen koppelen aan een Enum-waarde, dat is alles. Sterker nog, die wens is niet alleen eenvoudig, hij is ook ontzettend generiek.

Dat is een significante observatie. Gezien de generieke aard van het codeerprobleem, is er eigenlijk geen enkele reden waarom ik code zou moeten schrijven het probleem op te lossen.

Mijn taak

Dat klinkt misschien tegenintuïtief. Als softwareontwikkelaar is het toch bij uitstek jouw taak om codeproblemen op te lossen, generiek of niet? We doen de hele dag toch niet anders?

Ja en nee. Ja, professionele softwareontwikkelaars lossen problemen op met code. Welke problemen? In eerste instantie: businessproblemen. En pas in tweede instantie codeproblemen - problemen die ontstaan door de gekozen oplossing voor het businessprobleem.

Maar het is belangrijk om in het achterhoofd te houden (en dit is de “nee”): code is een middel, geen doel op zichzelf. Als ik een businessprobleem op kan lossen zonder daar zelf code voor te hoeven schrijven, heeft dat dan niet de voorkeur?

Waarom zou ik interfaces en singletons en Reflection gebruiken om code te kunnen koppelen aan een Enum, als er ook een stukje opensource software bestaat die dit probleem voor mij oplost? - Door de oplossing zelf te coderen, haal ik me codeproblemen op de hals die ik kan vermijden door de code uit te besteden.

De oplossing

De vraag wordt dan: kan ik dit probleem uitbesteden? En het antwoord is een volmondig “Ja!”, leerde ik dankzij de onderstaande video van Nick Chapsas:


De oplossing bevindt zich in het gebruik van de SmartEnum. Dat is een NuGet package dat je in staat stelt om logica te koppelen aan bepaalde Enum-waarden.

Ik zal niet stap voor stap uitleggen hoe dat er in code uitziet, daarvoor kun je de bovenstaande video bekijken of de documentatie doorpluizen.

In plaats daarvan zal ik het eindresultaat presenteren. Alle code die ik in de oorspronkelijke zes blogs heb opgetuigd, kan dankzij SmartEnums gereduceerd worden tot deze drie classes:

Er is geen ClaimProviderFactory meer nodig: alle code om de logica te koppelen aan een Enum-waarde wordt afgehandeld door de SmartPermission, die via een property Claim de juiste string gekoppeld heeft.1

Performance

Ik heb ook de boel gebenchmarkt om te zien of er performancewinst is behaald met deze oplossing. En ja hoor:

MethodGem.FoutmargeStandaarddev.Mediaan
GetClaimsForUserV07403.1 ns47.98 ns141.47 ns471.4 ns
GetClaimsForUserV00860.6 ns14.92 ns23.23 ns855.7 ns
GetClaimsForUserV01884.7 ns17.68 ns34.06 ns885.1 ns
GetClaimsForUserV02916.1 ns18.22 ns40.75 ns908.9 ns
GetClaimsForUserV06997.2 ns18.88 ns44.13 ns997.8 ns
GetClaimsForUserV055,114.2 ns652.18 ns1,922.98 ns5,894.1 ns
GetClaimsForUserV045,213.8 ns702.33 ns2,070.84 ns4,521.9 ns
GetClaimsForUserV0312,741.5 ns1,386.58 ns4,088.36 ns14,377.7 ns

Deze optie is twee keer zo snel als de oorspronkelijke oplossingsrichting, laat staan mijn refactorpogingen!

Les

Dit is precies waarom IT-managers graag dingen als “Buy before build!” roepen. De kans dat iemand een goede oplossing heeft bedacht voor een algemeen programmeerprobleem, is ontzettend groot. De kans dat een community aan opensource-developers een betere oplossing voor zo’n probleem kan bedenken dan ik, is nagenoeg 1.

De implicaties daarvan voor wat het betekent om een goede softwareontwikkelaar te zijn, zijn nauwelijks te overschatten. Het gaat er niet (meer?) om wie de beste code kan schrijven. Een ontwikkelaar die de beste oplossing kan vinden op het web, wint het van een vaardige programmeur, makkelijk.

De code die ik schrijf met SmartEnum is eenvoudiger én sneller. De les is duidelijk: gebruik de kracht van opensource voor algemene codeerproblemen. Iedereen wint, zo simpel is het.

Meer in deze reeks

  1. De casus
  2. Het Single-Responsibility principe
  3. Het Dependency inversion principe
  4. Het Open-closed principe
  5. SOLID en performance
  6. Conclusie
  7. Slimmere Enums

  1. Ik heb de oplossing bewust zo simpel mogelijk gehouden. De oorspronkelijke Enum had bijvoorbeeld een FlagsAttribute om het opslaan in de database te vergemakkelijken. Flags worden sinds kort ook ondersteund door SmartEnum, maar zijn buiten beschouwing gelaten omdat ze niet voor het punt van deze blog relevant zijn. ↩︎

clean code · enums · leermoment · open source · performance · refactoren · software ontwikkelaar (rol) · software ontwikkelen · solid · waarde