Hvad er SOLID-principperne?
SOLID-principperne består af fem retningslinjer for ren, vedligeholdelsesvenlig og fleksibel kode i objektbaseret programmering. Anvendelse og overholdelse af principperne resulterer i et letforståeligt software-design over lange udviklingsperioder. Med disse principper kan man ikke kun skrive bedre kode, men også vedligeholde eksisterende kode lettere.
Hvad er SOLID-principperne, og hvem har udviklet dem?
God kildekode starter med regler, programmeringsparadigmer og en passende programmeringsstil for effektiv og ren kode. Det er netop det, de fem SOLID-principper, der er opfundet af Robert C. Martin, Bertrand Meyer og Barbara Liskov, sikrer. Ved at følge disse principper i objektorienteret programmering (OOP) med sprog som Python eller Java skriver du ikke kun bedre kode, men sikrer også mere effektiv kodevedligeholdelse, bæredygtigt og fleksibelt software-design og større sikkerhed på lang sigt.
Navnet SOLID er repræsentativt for det solide programmeringsgrundlag, som alle, der ønsker at lære programmering, bør have. Akronymet blev skabt af Michael Feathers, der tog de første bogstaver i hvert af de fem principper for at skabe det:
- Princippet om enkeltansvar: En klasse bør have én og kun én grund til at ændre sig.
- Åben-lukket-princippet: Softwareenheder (klasser, moduler, funktioner osv.) bør være åbne for udvidelse, men lukkede for ændringer.
- Liskovs substitutionsprincip: Underklasser skal kunne arve og implementere alle metoder og egenskaber fra overklassen.
- Princippet om grænsefladesegregering: Grænseflader bør ikke indeholde flere metoder, end der er nødvendigt for at implementere klasser.
- Princippet om afhængighedsinversion: Klasser bør ikke være afhængige af andre klasser, men af grænseflader eller abstrakte klasser.
Hvilke fordele giver SOLID-principperne?
Hvor der ikke er regler, opstår der kaos, og dette bliver især mærkbart ved programmering. Selv små fejl, unøjagtigheder og mangler kan gøre god kildekode fuldstændig ubrugelig på lang sigt, hvis de ikke bliver “behandlet”. Nogle gange er det nok med komplekse klasser, der gør implementeringen vanskelig, eller underklasser, der mangler individuelle egenskaber fra deres overklasser. SOLID-principperne sikrer, at så lidt kode som muligt skal repareres med refaktorering.
SOLID-principperne skaber kode:
- Tydelig, ren og attraktiv: Software og koder er lettere at forstå, mere effektive og ser simpelthen bedre ud.
- Let at vedligeholde: Den enkle og klare struktur gør det lettere for flere samarbejdspartnere at vedligeholde og administrere både ny kode og ældre kode.
- Kan tilpasses, udvides og genbruges: Gennem forbedret læsbarhed, reduceret kompleksitet og ansvar samt mindre afhængighed af klasser kan koden bedre redigeres, tilpasses og udvides gennem grænseflader og genbruges fleksibelt.
- Mindre fejlbehæftet: Ren kode med en enkel struktur betyder, at ændringer i en del af koden ikke utilsigtet påvirker andre områder eller funktioner.
- Sikker og mere pålidelig: Reduktion eller eliminering af sårbarheder, uforeneligheder og fejl forbedrer et systems funktionalitet og pålidelighed, hvilket igen forbedrer sikkerheden.
Hvad betyder hvert af SOLID-principperne?
SOLID-principperne hører til de gyldne regler for god programmering og bør være velkendte for alle, der arbejder med objektbaseret programmering. Nedenfor forklarer vi hvert princip i detaljer.
SRP: Princippet om enkelt ansvar
Robert C. Martin skabte den oprindelige definition for dette princip i ‘Agile Software Development: Principles, Patterns and Practices’, hvor han skrev:
“Hvert softwaremodul bør have én og kun én grund til at ændre sig”.
Single Responsibility Principle (SRP) fastslår, at hver klasse i objektorienteret programmering (OOP) kun skal have ét ansvar. Dette betyder, at der kun skal være én grund til at ændre en klasse. I modsætning til almindelige fortolkninger betyder dette ikke, at en klasse eller et modul kun kan have én opgave. Det betyder snarere, at den kun skal være ansvarlig for specifikke opgaver, som ideelt set ikke overlapper med andre områder.
En overlapning af ansvarsområder, såsom at tilbyde funktioner til forskellige forretningssegmenter, kan påvirke klassens funktionalitet, når der foretages ændringer i et segment. At have flere ansvarsområder og mange afhængigheder kan medføre, at en enkelt ændring i et område medfører kodefejl eller behov for yderligere ændringer.
Single Responsibility Principle (SRP) er designet til at skabe sammenhængende moduler med specifikke, veldefinerede funktioner eller objekter. Takket være dets klare, strukturerede opsætning med minimale afhængigheder og uafhængige implementeringer kan ændringer og justeringer foretages mere effektivt, hurtigt og problemfrit.
Klaser, også kendt som objekttyper, er de centrale elementer i objektorienteret programmering (OOP). De kan ses som blueprints for objekter, der skitserer deres attributter, så det er muligt at genskabe virkelige objekter og koncepter i software. Klasser, også kendt som moduler, sammenlignes ofte med filtyper.
OCP: Åben-lukket-princippet
Ifølge Robert C. Martin og Bertrand Meyer i ‘Object Oriented Software Construction’ lyder OCP således:
“Softwareenheder (klasser, moduler, funktioner osv.) bør være åbne for udvidelse, men lukkede for ændringer”.
OCP sikrer, at du ikke behøver at omskrive softwarens kerne for at implementere ændringer. Hvis der kræves dybtgående kodemodifikationer, er der risiko for subtile fejl og kodelugt. En velstruktureret kode bør indeholde grænseflader, der kan bruges til at udvide den med yderligere funktioner. Nøgleordet her er klassearv.
Nye funktioner og udvidelser med klare nye funktioner og metoder, der skal implementeres, kan nemt tilføjes til en superklasse via en grænseflade i form af underklasser. På denne måde behøver du ikke at ændre den skrevne, stabile kode. Det forenkler vedligeholdelsen og opdateringen af software og forbedrer effektiviteten af genbrug af stabile kodeelementer via grænseflader betydeligt.
LSP: Liskovs substitutionsprincip
Ifølge Barbara H. Liskov og Jeannette M. Wing i ‘Behavioral Subtyping Using Invariants and Constraints’ fastslår LSP, at:
”Lad q(x) være en egenskab, der kan bevises for objekter x af typen T. Så bør q(y) kunne bevises for objekter y af typen S, hvor S er en undertype af T”.
Det lyder måske kryptisk, men det er faktisk ret nemt at forstå: Forbundne eller udvidede underklasser skal fungere som deres overklasser eller baseklasser. Det betyder, at hver underklasse skal bevare egenskaberne fra sin respektive overklasse gennem arv, og disse egenskaber må ikke ændres i underklassen. De skal i princippet kunne erstattes, deraf substitutionsprincippet. Overklasser kan derimod ændres.
For at forklare det, lad os se på det klassiske eksempel fra Robert C. Martin om rektangler og firkanter. I geometriundervisningen lærer vi følgende princip: alle firkanter er rektangler, men ikke alle rektangler er firkanter. En firkant har ikke kun retvinklede sider som rektangler, men alle dens sider er også lige lange.
I programmering fører antagelsen om, at lignende eller tilsyneladende identiske klasser er relaterede eller afhængige af hinanden, til fejl, misforståelser og uklar kode. Af denne grund er klassen ‘Rectangle’ i programmering ikke en firkant, og klassen ‘Square’ er ikke et rektangel. Begge er adskilt og implementeret separat. Uden en integreret forbindelse mellem klasserne kan misforståelser ikke føre til fejl på tværs af klasser. Dette øger sikkerheden og stabiliteten, når man bytter implementeringer i underklasser eller overklasser uden konsekvenser.
ISP: Princippet om grænsefladesegregering
Ifølge Robert C. Martin i “The Interface Segregation Principle” defineres ISP som følger:
“Kunder bør ikke tvinges til at være afhængige af grænseflader, som de ikke bruger”.
ISP fastslår, at brugerne ikke bør være nødt til at bruge grænseflader, som de ikke har brug for. Med andre ord: For at kunne tilbyde kunderne funktioner i bestemte klasser skræddersys nye, mindre grænseflader til specifikke krav. Dette forhindrer, at grænsefladerne bliver for store, og sikrer, at der ikke opstår stærke afhængigheder mellem klasserne. Fordelen er, at software med afkoblede klasser og flere små grænseflader, der er skræddersyet til specifikke krav, er nemmere at vedligeholde.
DIP: Afhængighedsinversionsprincippet
Ifølge Robert C. Martin i ‘The Dependency Inversion Principle’ er det femte og sidste af SOLID-principperne som følger:
”A. Moduler på højt niveau bør ikke være afhængige af moduler på lavt niveau. Begge bør være afhængige af abstraktioner. B. Abstraktioner bør ikke være afhængige af detaljer”.
DIP sikrer, at specifikke funktionaliteter og afhængigheder inden for kildekodelagene er baseret på abstrakte grænseflader og ikke direkte på hinanden. Softwarearkitekturer er typisk organiseret i højere brugerniveauer og lavere, mere abstrakte niveauer. Logisk set kunne man tro, at det abstrakte fundament påvirker de øverste lag. DIP identificerer imidlertid et potentielt problem her, da det skaber afhængigheder for de højere niveauer på de lavere niveauer, hvilket kan føre til problemer.
I stedet for at forbinde højere niveauer med lavere niveauer bør klasser på høje og lave niveauer afhænge af abstrakte, mellemliggende grænseflader. Grænsefladerne henter funktionaliteter, der er nødvendige på højere niveauer, fra lavere niveauer og gør dem tilgængelige. På denne måde kan man undgå en bottom-up-hierarki af afhængigheder, som med tiden kan føre til fejl i koden. Dette letter genbrugbarheden af moduler og gør det muligt at foretage ændringer i lavere klasser uden at påvirke højere niveauer.
Hvad sker der, hvis SOLID-principperne ikke overholdes?
At skabe ren, læsbar kode, der forenkler vedligeholdelsen, bør være et primært mål i softwareudviklingen. Hvis udviklere overser vigtige retningslinjer som SOLID-principperne, kan koden blive alvorligt forringet på grund af sårbarheder, redundanser, akkumulerede fejl og overdrevne afhængigheder. I ekstreme tilfælde kan koden blive ubrugelig med tiden. Dette er et væsentligt problem i agil softwareudvikling, hvor mange mennesker ofte arbejder med komplekse kodningsopgaver.
Konsekvenserne af uren kode eller dårlig vedligeholdelse af kode omfatter:
- Kodestank: Når kode ikke er skrevet i overensstemmelse med de nødvendige standarder, kan dette forårsage kodestank eller ‘stinkende kode’, hvilket kan føre til funktionsfejl og inkompatible programmer.
- Kodenedbrydning: Hvis koden ikke vedligeholdes eller repareres ved hjælp af refaktorering eller en kostbar kodegennemgang, kan den billedligt talt ‘nedbrydes’ og miste sin funktionalitet fuldstændigt. Et andet udtryk for ulæselig, indviklet kode er spaghettikode.
- Sikkerhedsrisici: De problemer, der opstår, er ikke begrænset til nedbrud, kompleks vedligeholdelse og kompatibilitetsproblemer. Der er også sikkerhedshuller, der giver malware mulighed for at udnytte koden, herunder zero-day-exploits.
Hvem udviklede SOLID-principperne?
SOLID-principperne stammer fra en række principper, der først blev introduceret af Robert C. Martin (“Uncle Bob”), en af initiativtagerne til agil programmering, i 2000 i hans essay med titlen “Design Principles and Design Patterns”. SOLID-principperne blev opfundet af Robert C. Martin, Bertrand Meyer og Barbara Liskov. Det fængende akronym blev populært af Michael Feathers, der omarrangerede de første bogstaver i de fem væsentlige principper i en let huskbar rækkefølge.
Hvilke lignende programmeringsprincipper findes der?
I softwareudvikling er principper generelle eller meget specifikke retningslinjer og anbefalinger til handling. Ud over SOLID-principperne, som blev udviklet til objektorienteret programmering, omfatter andre programmeringsprincipper for ren kode:
- DRY-princippet (Don’t repeat yourself) for funktioner med en enkelt, unik repræsentation
- KISS-princippet (Keep it simple, stupid) for kode, der er konstrueret så enkelt som muligt