Table of Contents
Inleiding: Waarom refactoring en SOLID hand in hand gaan
Elk softwaresysteem dat al meer dan een paar maanden actief is in de ontwikkeling, stapelt onvermijdelijk technische schulden op. Snelle oplossingen, veranderende eisen en de druk om nieuwe functies te verzenden leiden vaak tot code die kwetsbaar, moeilijk te begrijpen en moeilijk uit te breiden is. Twee praktijken vallen op als de meest effectieve tegengif tegen dit verval: refactoring en de SOLID principes[.
Refactoring is de gedisciplineerde techniek van het herstructureren van bestaande code zonder het externe gedrag te veranderen. Het lost geen bugs op of voegt functies toe; in plaats daarvan verbetert het de interne structuur zodat toekomstige veranderingen veiliger en sneller worden. De SOLID principes, geïntroduceerd door Robert C. Martin, bieden een set van ontwerprichtlijnen die, wanneer gevolgd, rendement code die onderhoudbaar, testbaar en veerkrachtig is om te veranderen. De twee disciplines zijn natuurlijke bondgenoten. Refactoring is het instrument dat u geleidelijk aan een bestaande codebase in overeenstemming met SOLID, zelfs wanneer het oorspronkelijke ontwerp was verre van ideaal.
In de praktijk hebben veel ontwikkelingsteams moeite om SOLID-principes met terugwerkende kracht toe te passen. De oorspronkelijke code kan monolithisch, strak gekoppeld of bezaaid zijn met voorwaardelijke logica. Zonder een systematische aanpak, voelt de poging om het SOLID te maken overweldigend. Dat is waar refactoringtechnieken schijnen. Door het werk te breken in kleine, gedragsbesparende stappen, kunt u geleidelijk een codebase transformeren totdat het zich aanpast aan elk SOLID-principe. Dit artikel onderzoekt concrete refactoringtechnieken voor elk van de vijf principes, compleet met praktische begeleiding over wat te zoeken en hoe verder te gaan.
De beginselen van de SOLID begrijpen
Voordat we in refactoringtechnieken gaan duiken, zal een korte samenvatting van het SOLID-acroniem het volgende doen:
- Een verantwoordelijkheidsgevoelsbeginsel (SRP): Een klasse moet slechts één reden hebben om te veranderen ..dat wil zeggen dat het één enkele, duidelijk omschreven verantwoordelijkheid moet hebben.
- Open/Gesloten Principe (OCP): Software-entiteiten (klassen, modules, functies) moeten open staan voor uitbreiding maar gesloten voor wijziging.
- Liskov Substitution Principle (LSP): Objecten van een superklasse moeten vervangbaar zijn met objecten van een subklasse zonder de juistheid van het programma te beïnvloeden.
- Interface Segregation Principle (ISP): Klanten mogen niet gedwongen worden afhankelijk te zijn van interfaces die ze niet gebruiken.
- Het Dependentship Inversion Principle (DIP): Hoogwaardige modules moeten niet afhankelijk zijn van laag-niveau modules; beide moeten afhankelijk zijn van abstracties.
Elk principe richt zich op een specifieke soort code geur. SRP vecht klassen die
Herfactoring van het beginsel van de gemeenschappelijke verantwoordelijkheid
Bepalen van inbreuken
Het meest voorkomende symptoom van een SRP overtreding is een klasse die meer dan één reden heeft om te veranderen. Bijvoorbeeld, een klasse genaamd die totalen berekent, de factuur voor weergave formatteert, het opslaan in een database, en stuurt een e-mail heeft minstens vier verantwoordelijkheden. Elke wijziging in de belastingberekening, HTML-opmaak, opslagschema, of e-mail inhoud zal een verandering in dezelfde klasse dwingen. Na verloop van tijd, de klasse wordt groot, strak gekoppeld, en moeilijk te testen.
Om deze overtredingen te spotten, kijk voor klasse namen die woorden zoals .Manager, . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Refactoringtechnieken
De primaire refactoring voor SRP is Uittrekselklasse. Je identificeert een reeks gerelateerde velden en methoden die een samenhangend concept vormen en ze naar een nieuwe klasse verplaatsen. Bijvoorbeeld, uit de klasse, kun je , , en . Elke nieuwe klasse heeft nu een enkele reden om te veranderen.
Als de logica over een paar methoden verspreid is in plaats van een hele klasse, gebruik Uittrekmethode om een bepaald deel van het gedrag te isoleren. Dit maakt de verantwoordelijkheid zichtbaarder en bereidt de grond voor op een toekomstige Extractklasse. Een verwante techniek is Beweeg methode] wanneer een methode logischer lijkt te behoren tot een andere klasse dan de huidige gastheer.
Een andere waardevolle techniek is Vervang Inline Code door Functie Call wanneer u herhaaldelijke logica die behoort tot een ander domein opmerkt. Door het verplaatsen van die logica naar een specifieke functie of klasse, vermindert u de primaire klasse oppervlakte en maakt verantwoordelijkheden expliciet. Het doel is dat elke klasse kan worden beschreven in een enkele zin zonder het woord
Refactoring toepassen voor het Open/Gesloten Principe
Voorwaardelijke gegevens vervangen door polymorfisme
Code die OCP schendt bevat vaak grote of verklaringen die een type of modus controleren. Bijvoorbeeld, een methode die de verzendkosten berekent op basis van een string , , of is gesloten voor nieuwe verzendmethoden. Het toevoegen van een nieuwe methode vereist het wijzigen van dat voorwaardelijke blok .. een directe schending van ..ingetrokken voor wijziging.
De standaard refactoring is hier Vervang Voorwaardelijk met Polymorfisme. Je maakt een abstracte basisklasse of interface (bijv. ) aan met een methode ]. Elke verzendmethode wordt een concrete subklasse. De oorspronkelijke clientcode gebruikt dan de abstractie, en nieuwe verzendmethoden worden toegevoegd door een nieuwe subklasse te creëren die open is voor uitbreiding, gesloten voor wijziging.
Gebruik van de strategie en decoratiepatronen
Het Strategie patroon is de klassieke OCP-driver. In de refactoringstap begin je meestal met het definiëren van de strategieinterface, en verplaats je vervolgens de voorwaardelijke branches in aparte strategieklassen. Tenslotte injecteer je de juiste strategie in de client op runtime. Dit gaat vaak hand in hand met Uittrekklasse en ]Introduceer parameterobject[ om de strategiemethoden schoon te houden.
Het Decorator patroon helpt wanneer je gedrag aan een object moet toevoegen zonder de kernklasse te veranderen. Bijvoorbeeld, als je een klasse hebt die platte tekst genereert, kun je het versieren met of zonder te wijzigen . Refactoreren naar het decoratorpatroon gaat meestal Extract Superclass[ of ]Convert Class naar Interface[ zodat zowel het kerncomponent als decorators een gemeenschappelijke abstractie delen.
Zelfs zonder formele ontwerppatronen, het principe van het bevorderen van compositie boven erfenis helpt OCP. Wanneer u nodig hebt om gedrag te variëren, componeer de klasse van kleinere verwisselbare onderdelen in plaats van het vullen van logica in de klasse zelf.
Het Liskov-substitutiebeginsel ondersteunen
Subtyping en gedragscontracten
LSP-schendingen komen vaak boven als methoden in een subklasse die onverwachte uitzonderingen maken, keren terug waar de basisklasse een geldig object teruggeeft, of verzwakken voorwaarden en versterken postvoorwaarden. Een klassiek voorbeeld is een klasse die erft van maar het / contract schendt omdat een vierkant beide dimensies gelijk moet houden.
De eerste refactoringstap is identificeer het contract. Gebruik Introduceer Assertion of Uitzondering vervangen door precondition Check] om impliciete contracten expliciet te maken. Als een subklasse het contract niet kan nakomen, moet je de erfenis verbreken. In het rechthoek/vierkant geval is het recht refactoring om de erfenis te vervangen door een gemeenschappelijke interface (bijv. ) die niet het / paar bevat. Beide [ en ] implementeren [ onafhankelijk.
Interfaces gebruiken om LSP te forceren
Een praktische benadering is om Interface uit de basisklasse te halen wanneer je subklassegedrag detecteert dat niet uitlijnt. Dan hangt de client alleen af van de interface. Als de interfacemethoden zo smal zijn dat elke implementatie er aan kan voldoen, is LSP automatisch tevreden. Bijvoorbeeld, in plaats van een basisklasse met een methode , definieer een interface . Zowel als kunnen implementeren als een abstracte klasse, maar alleen . Dit vermijdt forceren [[ om een lege methode te hebben die een uitzondering gooit.
Een andere nuttige refactoring is Push Down Method: als een methode in een superklasse alleen zinvol is voor sommige subklassen, verplaats het dan naar die subklassen. Dit elimineert het risico van een subklasse die een ongepaste methode erft. Op dezelfde manier beweegt Push Down Field] staat die niet universeel nodig is.
Uitvoering Interface Segregatie via Refactoring
Splitsing vetinterfaces
De ISP wordt vaak geschonden wanneer een enkele interface te veel methoden accumuleert. Bijvoorbeeld, een [ interface met , , , en [ dwingt een eenvoudige tekst-alleen printer om stub methoden te implementeren. De refactoring oplossing is Extract Interface[] (of Split Interface[]) om kleinere, meer samenhangende interfaces te creëren: , [, , etc. Elke client is dan alleen afhankelijk van de interfaces die hij eigenlijk nodig heeft.
Bij het splitsen, zoek naar groepen van methoden die vaak samen worden gebruikt door specifieke clients. Een veel voorkomende fout is het splitsen in vele kleine interfaces voortijdig. Doel voor rolinterfaces: een interface die een enkele mogelijkheid vertegenwoordigt die een client zou willen. Bijvoorbeeld, een interface zou en ] kunnen hebben; deze twee methoden zijn logisch gekoppeld en waarschijnlijk niet gescheiden.
Bestaande cliëntcode refactoreren
Zodra de vetinterface is gesplitst, moet je elke client refactoreren om alleen de relevante interface te implementeren.Dit is een mix van Wijzig Methode Signature (om de smallere interface te accepteren) en Rename Class[] (om de nieuwe rol weer te geven).Je moet mogelijk ook grote implementatieklassen afbreken: als één klasse zowel als ] implementeert, maar slechts sommige cliënten gebruiken scanning, is het perfect prima voor de klasse om beide te implementeren, zolang geen cliënt gedwongen wordt om van beide afhankelijk te zijn. Echter, als de klasse zelf te breed wordt, overwegen om een aparte scanklasse uit te halen (gebruik Extract Class[).
Refactoring voor het afhankelijkheidsinversiebeginsel
Abstracterende afhankelijkheden
Een typische DIP-overtreding is een klasse op hoog niveau, zoals ], die direct een laag niveau van klasse als inschakelt. Deze dwingt om afhankelijk te zijn van de concrete implementatie van de database, waardoor het moeilijk is om te testen en te ruilen met een in-geheugen repository. De eerste refactoring is Extract Interface] uit de afhankelijkheidsklasse: maak interface en maak implementeer het. Verander dan afhankelijk van de interface. Op dit punt heb je nog steeds een verborgen directe instantitatie .De volgende stap is Stel een parameter in (constructorinjectie) om de afhankelijkheid van buiten door te geven.
Afhankelijkheid Injectie en Inversie van Controle
De refactoring techniek Vervang Constructor door Fabrieksmethode kan worden gebruikt wanneer u niet gemakkelijk van constructeur kunt veranderen. Gebruik ook Vervang Global Reference door Parameter[] als de afhankelijkheid wordt verkregen uit een statische singleton of service locator. Geleidelijk aan, beweegt u zich naar het hebben van alle afhankelijkheden expliciet geïnjecteerd, meestal door de constructor. Dit maakt de klasse zuiver afhankelijk van abstracties en open om getest te worden met bespotten of stubs.
Zodra u constructorinjectie hebt, overweeg dan om Extract Method Object te gebruiken als de geïnjecteerde afhankelijkheden in veel methoden worden gebruikt . . Dat kan een teken zijn dat de klasse zelf nog te veel verantwoordelijkheden heeft. Kijk ook naar klassen die afhankelijk zijn van meerdere verschillende abstracties maar alleen gebruik maken van een deel van hun methoden; dat kan wijzen op een ISP-overtreding naast DIP.
Abstractions moeten bij de client horen, niet bij de concrete implementatie. Dit staat bekend als de Inversie van Ownership. Bij refactoring, definieer de abstractie (interface) in hetzelfde pakket als de module op hoog niveau die het gebruikt, niet in de module op laag niveau. Dit zorgt ervoor dat de module op hoog niveau niet afhankelijk is van iets dat de module op laag niveau bestuurt. Als uw interface in de bibliotheek op laag niveau is, verander het door de interfacedefinitie te verplaatsen naar het project op hoog niveau en de module op laag niveau te laten implementeren (ook bekend als Dependentance Inversion op pakketniveau).
Conclusie: Refactoring een gewoonte maken
Het versterken van SOLID principes door refactoring is geen eenmalige activiteit maar een voortdurende discipline. De technieken die hier beschreven worden . Extract Class, Replace Voorwaardelijk met Polymorfisme, Extract Interface, Introduce Parameter, en vele anderen . . zijn de bouwstenen die u toelaten om geleidelijk een codebase te hervormen zonder het te breken. Elke kleine stap vermindert technische schuld, maakt de code begrijpelijker, en opent de deur voor een eenvoudigere uitbreiding en testen.
Om uw praktijk te verdiepen, bestudeert u de catalogus van refactorings in Martin Foller. Refactoring website. Voor een gedetailleerde behandeling van SOLID principes, Robert C. Martin..................................................................................................................... ... ...... ............... ... ....... ................................................................
Start klein: kies een klasse die SRP schendt, gebruik Extract Class en zie hoe de rest van het systeem reageert. Het vertrouwen dat je krijgt zal je motiveren om het volgende principe aan te pakken. Met consistente praktijk, zul je deze refactorings internaliseren en beginnen met het ontwerpen van code die natuurlijk SOLID respecteert vanaf het begin.