Als je al een tijd meedraait in ons mooie vak, dan zal een design principle als SOLID je waarschijnlijk niet onbekend zijn. Althans, de term, de praktijk en hoe je de verschillende onderdelen van dit principe toepast, is vaak een stuk lastiger. Vandaag wil ik met je stil staan bij de eerste letter van dit acroniem, wat staat voor Single Responsibility.

Wat is Single Responsibility

Letterlijk vertaald, gaat dit principe erover dat een klasse of module één heldere verantwoordelijkheid heeft. Een andere benadering om dit principe te beoordelen is dat een klasse één reden zou moeten hebben waardoor de code gewijzigd zou moeten worden. Dat is de theorie, maar de praktijk is een stuk lastiger. Stel bijvoorbeeld dat je de volgende klasse hebt gemaakt:

Op het eerste oog valt het in het voorbeeld van de BankAccount klasse op dat deze zowel functies bevat met betrekking tot het storten en opnemen van geld, en een methode om transacties te printen. Dat zijn functioneel behoorlijk verschillende dingen, en waarschijnlijk is het beter om deze zaken op te splitsen. In de praktijk zijn er mogelijk ook nog een hoop technische details die in deze klasse geïmplementeerd worden. Denk bijvoorbeeld aan interactie met een database, of berichten die naar een queue moeten, of in het geval van de PrintStatement methode: de logica om iets naar een printer te sturen. De vraag is “Over welke verantwoordelijkheid hebben we het als het?” Is dat een functionele verantwoordelijkheid, een technische of een combinatie.

Kijk je functioneel of vanuit technisch perspectief?

Wanneer je vanuit functioneel perspectief kijkt naar Single Responsibility principle dan zul je er waarschijnlijk geen moeite mee hebben om allerlei technische implementatiedetails in één klasse te zetten. Bijvoorbeeld een BankAccount klasse die gewoon rechtstreekse interactie met de database heeft. In de praktijk zul je dan vaak andere designprincipes overtreden (bijvoorbeeld omdat database code die in essentie hetzelfde doet op heel veel plekken herhaald wordt). In de praktijk zie je vaak dat er zowel vanuit een functioneel perspectief als een technisch perspectief naar de code gekeken wordt.

Hoe beoordeel je “axes of change”?

Een goede omschrijving van Single Responsibility is dat een klasse of module maar één reden mag hebben waarom deze aangepast moet worden. In ons voorbeeld van het BankAccount hebben we logica om geld te storten en op te nemen, en we hebben logica om transacties uit te printen. Je voelt wel aan dat dit twee behoorlijk verschillende zaken zijn. Wanneer je dit bij elkaar in één implementatie zet, dan zou een wijziging in het één ongewenst invloed kunnen hebben op de logica om het ander.

Oeps… volgens mij zijn we te ver gegaan?

In de praktijk is het toepassen van dit soort design principes best lastig. Ik kom tal van voorbeelden tegen waar teams met de beste bedoelingen abstracties en lagen hebben geïntroduceerd en in de praktijk vervolgens slechter af zijn. Gevolg is bijvoorbeeld dat één wijziging op soms wel tientallen plekken in de code impact heeft. Hmmm… dat is misschien ook niet de bedoeling.

Hoe ga je dan om met design principes in de praktijk?

Eerst maar het slechte nieuws: je zult er meestal pas achteraf achter komen wat je vooraf anders had moeten doen. Dat betekent dat we mild voor onszelf moeten zijn wanneer we gaandeweg zaken aanpassen op basis van voortschrijdend inzicht. De eerste jaren van mijn werkzame leven heb ik veel structuren bedacht die achteraf nodeloos ingewikkeld bleken. Ik adviseer je eens te kijken naar de principes achter “emergent architecture”. Wat betekent dit in de praktijk? Dat je bepaalde structuren aanbrengt omdat je, op basis van je ervaringen uit het verleden, weet dat ze meerwaarde hebben. En voor andere beslissingen en abstracties, stel je ze uit totdat je op een punt komt dat ze meerwaarde hebben.

Wil je een keer sparren over je architectuur?

Ik maak graag een keer tijd voor je vrij. Je kunt ook bij ons terecht voor een workshop voor je team. Ik hoor graag van je.

mjongerius@bergler.nl