De S van SOLID: Het Single Responsibility Principe

door | mrt 4, 2021 | Software Ontwikkeling | 0 Reacties

Vandaag start ik met een 5-delige blog-serie die gaat over SOLID: vijf software design principes om code meer begrijpelijk, flexibel en onderhoudbaar te maken. Ik kom regelmatig nog software ontwikkelaars tegen die nog niet weten wat SOLID is. Dat is jammer. Want het zijn hele simpele maar krachtige principes die de kwaliteit van de code, waaraan meerdere ontwikkelaars werken, enorm kan verbeteren.

Single Responsibility Principe

We beginnen bij de S van single responsibility. Dit principe gaat erover dat je als alleenstaande man of vrouw goed en verantwoordelijk om moet gaan met.. Oh nee, dat is iets tè letterlijk! Maar de vertaling klopt wel: het gaat over het hebben van maar één verantwoordelijkheid.

In veel software talen werk je met zogenaamde klassen (verder ga ik dit class/classes noemen). Dit is een manier om code te organiseren. Vaak zit er in één bestand ook maar één class. De class heeft een naam en in de class kun je variabelen en functies definiëren. Een class is eigenlijk een stukje software waarin je data (in de vorm van variabelen) kunt opslaan en kunt bewerken (door middel van functies). De class is onderdeel van een groter geheel (bijv. een module).

# ../code/gebruiker.py

# Een class waarin een gebruiker wordt gedefinieerd
class User:

    # Variabele
    email = boaz@apilogic.nl

    # Functie
    def print_email(self):
        print('Het email-adres is: ' + email)

Het probleem

Wie wel eens in een grotere code-base heeft rondgekeken, is ongetwijfeld hele grote classes tegengekomen. Ik ben in productieomgevingen classes tegengekomen van een wel 10.000 regels code. Dat komt dus neer op een document/bestand van 10.000 regels code. Een compleet boekwerk. Dit is een ramp om de code te gaan begrijpen, daar kun je gerust een hele middag voor gaan zitten. En waar moet je in hemelsnaam beginnen? Je vraagt je natuurlijk af hoe zoiets kan gebeuren. Zo’n class was op de eerste dag natuurlijk geen 10.000 regels code. Laat mij dat uitleggen hoe dat komt.

Stel: je bent ontwikkelaar en je wilt een nieuwe stukje functionaliteit toevoegen aan bestaande code. Die nieuwe functionaliteit moet natuurlijk op een bepaalde plek komen te staan. Wanneer je deze functionaliteit wilt toevoegen, ben je al snel geneigd de bestaande code aan te vullen met de nieuwe code. Dit is namelijk de kortste klap. En waar doe je dat? In de bijbehorende class natuurlijk.

Wanneer iedereen dit doet, komt er steeds meer functionaliteit in de class te staan. En hoe meer een class kan, hoe meer verantwoordelijkheden de class krijgt. Bijvoorbeeld: de class is niet alleen verantwoordelijk voor het bewaren van een email-adres, maar ook voor het versturen van een email. Door steeds maar code toe te voegen aan de class, verandert de class langzaam in een zelfstandig software pakket. Wat nu?

Gelukkig heb je sinds vandaag een houvast. Je stelt jezelf simpelweg de vraag: welke verantwoordelijkheid heeft deze class. Hoort deze functionaliteit wel in deze class of is het zaak om hier een nieuwe class voor te maken? Als handvat kun je een lengte van ±100 regels per class aanhouden. Wordt de class langer, dan is de kans groot dat je meer functionaliteit in je classes stopt dan gezond is voor je software (en voor je collega’s).

Eén verantwoordelijkheid betekent één reden om te veranderen

De bedenker van het single responsibility principe legde het als volgt uit: één verantwoordelijkheid betekent één reden om te veranderen. Ontwerp geen software door een systeem in kleinere componenten op te breken op basis van een flowchart. Maar begin met een lijst van moeilijke ontwerpbeslissingen die aan verandering onderhevig zijn. Elke module is ontworpen om zulke beslissingen van andere modules te verbergen.

Stel je een softwaremodule voor die automatisch je ongelezen mail ontvangt en hier vervolgens een automatische reactie op schrijft. Zo’n module heeft twee verantwoordelijkheden: de ongelezen mail ontvangen en een automatische reactie produceren. Wanneer je je houdt aan dit principe, dan zouden deze twee verantwoordelijkheden in aparte classes moeten worden ondergebracht. Mocht je ooit van e-mailprovider veranderen, dan hoeft de class die de automatische reactie schrijft daar niets van te weten. Je zou deze niet hoeven aan te raken.

Maar heb je je niet aan het principe gehouden? En je moet een class aanpassen waarin beide verantwoordelijkheden zijn verweven? Dan kun je in de problemen komen. Het kan maar zo gebeuren dat er functies of variabelen in de class zitten die door de het hele document gebruikt worden. Wanneer je die aanpast, heb je kans dat de inhoud van de automatische reacties ook verandert. Maar maak je een scheiding maakt op basis van verantwoordelijkheden (op de reden waarom de code in de toekomst kan gaan veranderen, heb je een garantie dat bij de verandering je andere code niet hoeft aan te raken. En iedereen weet: als je software niet aanraakt, blijft het (99,9% van de gevallen) gewoon doen wat het altijd deed.

“Do One Thing And Do It Well.”

Waarom is het nog meer goed voor een class om maar één verantwoordelijkheid te hebben?

Begrijpbaarheid – Stel je voor dat je door code heen aan het lopen bent. Begrijpend code lezen, zullen we maar zeggen. Wanneer je dan verschillende classes met goede namen tegenkomt (EmailSender of EmailValidator), dan weet je onmiddellijk welke verantwoordelijkheid deze classes hebben. Als je functionaliteit wilt toevoegen aan de manier waarop e-mails worden verzonden, weet je direct waar je moet kijken. Dit scheelt weer een hoop zoekwerk. En zeg nou zelf: dit leest toch ook veel lekkerder dan een InboxMonitorAndEmailValidator

Flexibiliteit – Wanneer je een class niet meer nodig hebt, kun je deze direct weggooien. Bij grote classes heb je kans dat de functionaliteit onderling verweven is geraakt. Het aanpassen van de class kan onverwachte gevolgen hebben voor de werking van je code op andere plekken. Met kleine modules heb je meer keuze in wat je gaat maken en hoe je dat doet. Dit is ook heel intuïtief: je kunt met grote duploblokken ook geen technisch lego kunt namaken.

Onderhoudbaarheid – Kleine classes zijn gemakkelijk te onderhouden. In een oogopslag zie je wat voor data er in en uit de class gaat. Kleine classes zijn gemakkelijk te (unit)testen omdat ze maar één hoeven te doen. De functionaliteit is overzichtelijk en je kunt aanpassingen snel doorvoeren.

Als je het tot hier hebt gered, dan wil ik je hartelijk bedanken voor je tijd! Ik hoop dat je wat van deze blog hebt geleerd of dat het een goede opfrisser was van wat weggezakte kennis. Wil je hier over doorpraten, stuur mij een berichtje!