Wat zijn de SOLID-principes?
De SOLID-principes bestaan uit vijf richtlijnen voor schone, onderhoudbare en flexibele code in objectgeoriënteerd programmeren. Door deze principes toe te passen en na te leven, ontstaat er een begrijpelijk softwareontwerp dat ook na lange ontwikkelingsperiodes nog steeds goed functioneert. Met deze principes kan niet alleen betere code worden geschreven, maar kan ook bestaande code gemakkelijker worden onderhouden.
Wat zijn de SOLID-principes en wie heeft ze ontwikkeld?
Goede broncode begint met regels, programmeerparadigma’s en een geschikte programmeerstijl voor efficiënte en schone code. Dit is precies wat de vijf SOLID-principes, bedacht door Robert C. Martin, Bertrand Meyer en Barbara Liskov, garanderen. Door deze principes te volgen bij objectgeoriënteerd programmeren (OOP) met talen als Python of Java, schrijf je niet alleen betere code, maar zorg je ook voor efficiënter codeonderhoud, duurzaam en flexibel softwareontwerp en meer veiligheid op de lange termijn.
De naam SOLID staat voor de solide programmeerbasis die iedereen die wil leren programmeren zou moeten hebben. Het acroniem zelf is bedacht door Michael Feathers, die de eerste letters van elk van de vijf principes heeft gebruikt om het te creëren:
- Enkelvoudig verantwoordelijkheidsprincipe: Een klasse mag slechts één reden hebben om te veranderen.
- Open-gesloten principe: software-entiteiten (klassen, modules, functies, enz.) moeten openstaan voor uitbreiding, maar gesloten zijn voor wijzigingen.
- Liskov-substitutieprincipe: Subklassen moeten alle methoden en eigenschappen van de superklasse kunnen overnemen en implementeren.
- Interface-segregatieprincipe: Interfaces mogen niet meer methoden bevatten dan nodig is voor het implementeren van klassen.
- Principe van afhankelijkheidsomkering: Klassen mogen niet afhankelijk zijn van andere klassen, maar van interfaces of abstracte klassen.
Welke voordelen bieden de SOLID-principes?
Waar geen regels zijn, ontstaat chaos, en dit wordt vooral merkbaar bij het programmeren. Zelfs kleine fouten, onnauwkeurigheden en hiaten kunnen goede broncode op de lange termijn volledig onbruikbaar maken als ze ‘onbehandeld’ blijven. Soms zijn complexe klassen die de implementatie bemoeilijken of subklassen die individuele eigenschappen van hun superklassen missen, al voldoende. De SOLID-principes zorgen ervoor dat er zo min mogelijk code hoeft te worden gerepareerd met refactoring.
SOLID-principes maken code:
- Duidelijk, overzichtelijk en aantrekkelijk: software en codes zijn gemakkelijker te begrijpen, effectiever en zien er gewoonweg beter uit.
- Eenvoudig te onderhouden: dankzij de eenvoudige en duidelijke structuur is het voor meerdere medewerkers eenvoudiger om zowel nieuwe code als legacycode te onderhouden en te beheren.
- Aanpasbaar, uitbreidbaar, herbruikbaar: door verbeterde leesbaarheid, verminderde complexiteit en verantwoordelijkheden, en verminderde afhankelijkheid van klassen, kan code beter worden bewerkt, aangepast en uitgebreid via interfaces, en flexibel worden hergebruikt.
- Minder foutgevoelig: schone code met een eenvoudige structuur betekent dat wijzigingen in een deel van de code niet per ongeluk andere delen of functies beïnvloeden.
- Veiliger en betrouwbaarder: Door kwetsbaarheden, incompatibiliteiten en fouten te verminderen of te elimineren, worden de functionaliteit en betrouwbaarheid van een systeem verbeterd, wat op zijn beurt de veiligheid ten goede komt.
Wat betekenen de SOLID-principes precies?
De SOLID-principes behoren tot de gouden regels voor goed programmeren en zouden bekend moeten zijn bij iedereen die met objectgeoriënteerd programmeren werkt. Hieronder leggen we elk principe in detail uit.
SRP: Principe van enkele verantwoordelijkheid
Robert C. Martin heeft de oorspronkelijke definitie voor dit principe opgesteld in ‘Agile Software Development: Principles, Patterns and Practices’, waarin hij schrijft:
“Elke softwaremodule moet één en slechts één reden hebben om te veranderen”.
Het Single Responsibility Principle (SRP) stelt dat elke klasse in objectgeoriënteerd programmeren (OOP) slechts één verantwoordelijkheid mag hebben. Dit houdt in dat er slechts één reden mag zijn om een klasse te wijzigen. In tegenstelling tot wat vaak wordt gedacht, betekent dit niet dat een klasse of module slechts één taak mag hebben. Het betekent veeleer dat deze alleen verantwoordelijk mag zijn voor specifieke taken die idealiter niet overlappen met andere gebieden.
Een overlapping van verantwoordelijkheden, zoals het aanbieden van functies voor verschillende bedrijfssegmenten, kan van invloed zijn op de functionaliteit van de klasse wanneer er wijzigingen worden aangebracht in één segment. Het hebben van meerdere verantwoordelijkheden en talrijke afhankelijkheden kan ertoe leiden dat een enkele wijziging op één gebied codefouten veroorzaakt of dat er aanvullende wijzigingen nodig zijn.
Het Single Responsibility Principle (SRP) is bedoeld om samenhangende modules te creëren met specifieke, duidelijk omschreven functies of objecten. Dankzij de duidelijke, gestructureerde opzet met minimale afhankelijkheden en onafhankelijke implementaties kunnen wijzigingen en aanpassingen efficiënter, sneller en soepeler worden doorgevoerd.
Klassen, ook wel objecttypen genoemd, zijn de centrale elementen in objectgeoriënteerd programmeren (OOP). Ze kunnen worden gezien als blauwdrukken voor objecten, waarin hun kenmerken worden beschreven, zodat het mogelijk is om objecten en concepten uit de echte wereld na te bootsen in software. Klassen, ook wel modules genoemd, worden vaak vergeleken met bestandstypen.
OCP: Open Closed Principle (Open-gesloten principe)
Volgens Robert C. Martin en Bertrand Meyer in ‘Object Oriented Software Construction’ luidt het OCP als volgt:
“Software-entiteiten (klassen, modules, functies, enz.) moeten openstaan voor uitbreiding, maar gesloten zijn voor wijzigingen”.
Het OCP zorgt ervoor dat u de kern van de software niet hoeft te herschrijven om wijzigingen door te voeren. Als er ingrijpende codewijzigingen nodig zijn, bestaat het risico op subtiele fouten en code smells. Een goed gestructureerde code moet interfaces bieden die kunnen worden gebruikt om deze uit te breiden met extra functies. Het sleutelwoord hier is klasse-overerving.
Nieuwe functies en uitbreidingen met duidelijke nieuwe functies en methoden die moeten worden geïmplementeerd, kunnen eenvoudig aan een superklasse worden toegevoegd via een interface in de vorm van subklassen. Op deze manier hoeft u niet te knoeien met de geschreven, stabiele code. Het vereenvoudigt het onderhoud en de instandhouding van software en verbetert aanzienlijk de efficiëntie van het hergebruik van stabiele code-elementen via interfaces.
LSP: Liskovs substitutieprincipe
Volgens Barbara H. Liskov en Jeannette M. Wing in ‘Behavioral Subtyping Using Invariants and Constraints’ stelt de LSP dat:
“Laat q(x) een eigenschap zijn die bewijsbaar is voor objecten x van type T. Dan moet q(y) bewijsbaar zijn voor objecten y van type S, waarbij S een subtype is van T”.
Het klinkt misschien cryptisch, maar het is eigenlijk heel eenvoudig te begrijpen: gekoppelde of uitgebreide subklassen moeten functioneren zoals hun superklassen of basisklassen. Dit betekent dat elke subklasse de eigenschappen van zijn respectieve superklasse moet behouden door middel van overerving en dat deze eigenschappen niet mogen worden gewijzigd in de subklasse. Ze moeten in principe vervangbaar zijn, vandaar het substitutieprincipe. Superklassen daarentegen mogen worden gewijzigd.
Laten we ter verduidelijking eens kijken naar het klassieke voorbeeld van Robert C. Martin over rechthoeken en vierkanten. In de meetkundeles leren we het volgende principe: elk vierkant is een rechthoek, maar niet elke rechthoek is een vierkant. Een vierkant heeft niet alleen rechte hoeken zoals rechthoeken, maar alle zijden zijn ook even lang.
Bij het programmeren leidt het aannemen dat vergelijkbare of schijnbaar identieke klassen aan elkaar gerelateerd of van elkaar afhankelijk zijn tot fouten, misverstanden en onduidelijke code. Om deze reden is in programmeren de klasse ‘Rechthoek’ geen vierkant en is de klasse ‘Vierkant’ geen rechthoek. Beide zijn ontkoppeld en worden afzonderlijk geïmplementeerd. Zonder een geïntegreerde verbinding tussen de klassen kunnen misverstanden niet leiden tot fouten tussen klassen. Dit verhoogt de veiligheid en stabiliteit bij het omwisselen van implementaties in subklassen of superklassen, zonder repercussies.
ISP: Interface Segregation Principle (principe van interfacesegregatie)
Volgens Robert C. Martin in ‘The Interface Segregation Principle’ wordt ISP als volgt gedefinieerd:
“Klanten mogen niet worden gedwongen om afhankelijk te zijn van interfaces die ze niet gebruiken”.
De ISP stelt dat gebruikers geen interfaces moeten gebruiken die ze niet nodig hebben. Met andere woorden: om klanten de functies van bepaalde klassen te kunnen bieden, worden nieuwe, kleinere interfaces op maat gemaakt voor specifieke vereisten. Dit voorkomt dat interfaces te groot worden en zorgt ervoor dat er geen sterke afhankelijkheden tussen klassen ontstaan. Het voordeel is dat software met ontkoppelde klassen en verschillende kleine interfaces die op maat zijn gemaakt voor specifieke vereisten, gemakkelijker te onderhouden is.
DIP: Principe van afhankelijkheidsomkering
Volgens Robert C. Martin in ‘The Dependency Inversion Principle’ luidt het vijfde en laatste SOLID-principe als volgt:
“A. Modules op hoog niveau mogen niet afhankelijk zijn van modules op laag niveau. Beide moeten afhankelijk zijn van abstracties. B. Abstracties mogen niet afhankelijk zijn van details”.
Het DIP zorgt ervoor dat specifieke functionaliteiten en afhankelijkheden binnen broncodelaagjes op abstracte interfaces vertrouwen, en niet direct op elkaar. Softwarearchitecturen zijn doorgaans georganiseerd in hogere gebruikersniveaus en lagere, meer abstracte niveaus. Logischerwijs zou je kunnen denken dat de abstracte basis het gedrag van de bovenste lagen beïnvloedt. Het DIP signaleert hier echter een potentieel probleem, omdat het afhankelijkheden creëert voor de hogere niveaus op de lagere niveaus, wat tot problemen kan leiden.
In plaats van hogere niveaus aan lagere niveaus te koppelen, moeten klassen in hoge en lage niveaus afhankelijk zijn van abstracte, tussenliggende interfaces. De interfaces halen functionaliteiten die op hogere niveaus nodig zijn uit lagere niveaus en maken deze beschikbaar. Op deze manier kan een bottom-up hiërarchie van afhankelijkheden worden vermeden, wat na verloop van tijd tot fouten in de code kan leiden. Dit vergemakkelijkt de herbruikbaarheid van modules en maakt wijzigingen in lagere klassen mogelijk zonder dat dit gevolgen heeft voor hogere niveaus.
Wat gebeurt er als de SOLID-principes niet worden nageleefd?
Het creëren van schone, leesbare code die het onderhoud vereenvoudigt, moet een primaire doelstelling zijn bij softwareontwikkeling. Als ontwikkelaars essentiële richtlijnen zoals de SOLID-principes over het hoofd zien, kan de code ernstig verslechteren als gevolg van kwetsbaarheden, redundantie, opgehoopte fouten en buitensporige afhankelijkheden. In extreme gevallen kan de code na verloop van tijd onbruikbaar worden. Dit is een belangrijk probleem bij agile softwareontwikkeling, waar vaak veel mensen aan complexe coderingstaken werken.
De gevolgen van onzuivere code of slecht codeonderhoud zijn onder meer:
- Code smell: Wanneer code niet volgens de vereiste normen is geschreven, kan dit leiden tot code smell of ‘smelly code’, wat functionele fouten en incompatibele programma’s tot gevolg kan hebben.
- Code rot: Als code niet wordt onderhouden of gerepareerd door middel van refactoring of een kostbare codereview, kan code figuurlijk ‘rotten’ en zijn functionaliteit volledig verliezen. Een andere term voor onleesbare, ingewikkelde code is spaghetticode.
- Beveiligingsrisico’s: De problemen die zich voordoen, zijn niet beperkt tot storingen, complex onderhoud en compatibiliteitsproblemen. Er zijn ook beveiligingslekken die malware de mogelijkheid bieden om de code te misbruiken, waaronder zero-day exploits.
Wie heeft de SOLID-principes ontwikkeld?
De oorsprong van de SOLID-principes ligt in verschillende principes die voor het eerst werden geïntroduceerd door Robert C. Martin (‘Uncle Bob’), een van de initiatiefnemers van agile programmeren, in het jaar 2000 in zijn essay getiteld ‘Design Principles and Design Patterns’. De SOLID-principes zijn bedacht door Robert C. Martin, Bertrand Meyer en Barbara Liskov. De pakkende afkorting werd populair gemaakt door Michael Feathers, die de beginletters van de vijf essentiële principes herschikte tot een gemakkelijk te onthouden volgorde.
Welke vergelijkbare programmeerprincipes zijn er?
In softwareontwikkeling zijn principes algemene of zeer specifieke richtlijnen en aanbevelingen voor actie. Naast de SOLID-principes, die zijn ontwikkeld voor objectgeoriënteerd programmeren, zijn er nog andere programmeerprincipes voor schone code, waaronder:
- DRY-principe (Don’t repeat yourself) voor functies met een enkele, unieke representatie
- KISS-principe (Keep it simple, stupid) voor code die zo eenvoudig mogelijk is opgebouwd