echt modulaire Code maken zonder afhankelijkheden

software ontwikkelen is geweldig, maar … ik denk dat we het er allemaal over eens zijn dat het een beetje een emotionele achtbaan kan zijn. In het begin is alles geweldig. U voegt nieuwe functies na elkaar in een zaken van dagen, zo niet uren. Je bent goed bezig!

spoel een paar maanden vooruit en uw ontwikkelingssnelheid neemt af. Is het omdat je niet zo hard werkt als voorheen? Niet echt. Laten we nog een paar maanden doorspoelen, en je ontwikkelingssnelheid daalt verder. Werken aan dit project is niet leuk meer en is een sleur geworden.

het wordt erger. Je begint meerdere bugs te ontdekken in je applicatie. Vaak creëert het oplossen van een bug twee nieuwe. Op dit punt kun je beginnen met zingen:

99 kleine bugs in de code.99 kleine beestjes.Haal er een neer, patch het rond,

… 127 kleine bugs in de code.

wat vindt u ervan om nu aan dit project te werken? Als je net als ik bent, verlies je waarschijnlijk je motivatie. Het is gewoon een pijn om deze applicatie te ontwikkelen, omdat elke verandering in bestaande code onvoorspelbare gevolgen kan hebben.

deze ervaring is gebruikelijk in de softwarewereld en kan verklaren waarom zoveel programmeurs hun broncode willen weggooien en alles willen herschrijven.

redenen waarom Software-Ontwikkeling in de loop der tijd vertraagt

Wat is de reden voor dit probleem?

de belangrijkste oorzaak is de toenemende complexiteit. Uit mijn ervaring is de grootste bijdrage aan de algehele complexiteit het feit dat in de overgrote meerderheid van de softwareprojecten alles met elkaar verbonden is. Vanwege de afhankelijkheden die elke klasse heeft, als u een aantal code in de klasse die e-mails te verzenden wijzigen, uw gebruikers kunnen plotseling niet registreren. Waarom is dat? Omdat uw registratiecode afhankelijk is van de code die e-mails verzendt. Nu kun je niets veranderen zonder bugs te introduceren. Het is gewoon niet mogelijk om alle afhankelijkheden te traceren.

dus daar heb je het; de echte oorzaak van onze problemen is het verhogen van de complexiteit afkomstig van alle afhankelijkheden die onze code heeft.

Big Ball of Mud and How to Reduce It

het grappige is dat dit probleem al jaren bekend is. Het is een algemeen anti-patroon genaamd de ” grote bal van modder.”Ik heb dat type architectuur gezien in bijna alle projecten waar ik in de loop der jaren aan heb gewerkt in meerdere verschillende bedrijven.

Wat is dit anti-patroon precies? Simpel gezegd, krijg je een grote bal modder wanneer elk element een afhankelijkheid heeft met andere elementen. Hieronder kunt u een grafiek zien van de afhankelijkheden van het bekende open-source project Apache Hadoop. Om de grote bal modder (of beter gezegd, de grote bal garen) te visualiseren, teken je een cirkel en plaats je er klassen van het project gelijkmatig op. Teken gewoon een lijn tussen elk paar klassen die van elkaar afhankelijk zijn. Nu kun je de bron van je problemen zien.

een visualisatie van Apache Hadoop ' s grote modderbal, met een paar dozijn knooppunten en honderden lijnen die ze met elkaar verbinden.

Apache Hadoop ‘ s “big ball of mud”

een oplossing met modulaire Code

dus stelde ik mezelf de vraag: zou het mogelijk zijn om de complexiteit te verminderen en nog steeds plezier te hebben zoals aan het begin van het project? Om eerlijk te zijn, je kunt niet alle complexiteit elimineren. Als u nieuwe functies wilt toevoegen, moet u altijd de complexiteit van de code verhogen. Toch kan complexiteit worden verplaatst en gescheiden.

hoe andere industrieën dit probleem oplossen

denk aan de mechanische industrie. Wanneer sommige kleine mechanische winkel is het maken van machines, kopen ze een set van standaard elementen, maken een paar aangepaste degenen, en zet ze samen. Ze kunnen deze componenten volledig afzonderlijk te maken en monteren alles aan het einde, het maken van slechts een paar tweaks. Hoe is dit mogelijk? Ze weten hoe elk element in elkaar zal passen door de industriestandaarden zoals bouten maten, en up-front beslissingen zoals de grootte van de montage gaten en de afstand tussen hen.

een technisch diagram van een fysiek mechanisme en hoe de stukken in elkaar passen. De stukken zijn genummerd in de volgorde waarin ze vervolgens bevestigd moeten worden, maar die volgorde gaat van links naar rechts 5, 3, 4, 1, 2.

elk element in de assemblage hierboven kan worden geleverd door een afzonderlijk bedrijf dat geen enkele kennis heeft van het eindproduct of de andere onderdelen ervan. Zolang elk modulair element volgens de specificaties wordt vervaardigd, kunt u het uiteindelijke apparaat maken zoals gepland.

kunnen we dat herhalen in de software-industrie?

zeker weten! Door het gebruik van interfaces en inversie van controle principe; het beste deel is het feit dat deze aanpak kan worden gebruikt in elke object-georiënteerde taal: Java, C#, Swift, TypeScript, JavaScript, PHP—de lijst gaat maar door. U hoeft geen fancy framework nodig om deze methode toe te passen. Je moet je aan een paar simpele regels houden en gedisciplineerd blijven.

inversie van controle is uw vriend

toen ik voor het eerst hoorde over inversie van controle, realiseerde ik me meteen dat ik een oplossing had gevonden. Het is een concept van het nemen van bestaande afhankelijkheden en ze inverteren met behulp van interfaces. Interfaces zijn eenvoudige verklaringen van methoden. Ze bieden geen concrete implementatie. Als gevolg hiervan kunnen ze worden gebruikt als een overeenkomst tussen twee elementen over hoe ze te verbinden. Ze kunnen worden gebruikt als een modulaire connectoren, als je wilt. Zolang een element de interface biedt en een ander element de implementatie ervan, kunnen ze samenwerken zonder iets van elkaar te weten. Het is briljant.

laten we eens kijken in een eenvoudig voorbeeld hoe we ons systeem kunnen ontkoppelen om modulaire code te creëren. De onderstaande diagrammen zijn geà mplementeerd als eenvoudige Java-toepassingen. Je kunt ze vinden op deze GitHub repository.

probleem

laten we aannemen dat we een zeer eenvoudige toepassing hebben die alleen bestaat uit een Main klasse, drie services en een enkele Util klasse. Die elementen zijn op meerdere manieren van elkaar afhankelijk. Hieronder, kunt u een implementatie te zien met behulp van de” big ball of mud ” aanpak. Klassen bellen elkaar gewoon. Ze zijn nauw met elkaar verbonden, en je kunt niet zomaar één element eruit halen zonder anderen aan te raken. Toepassingen gemaakt met behulp van deze stijl kunt u in eerste instantie snel groeien. Ik geloof dat deze stijl is geschikt voor proof-of-concept projecten, omdat je kunt spelen met dingen gemakkelijk. Toch is het niet geschikt voor productieklare oplossingen, omdat zelfs onderhoud gevaarlijk kan zijn en elke verandering onvoorspelbare bugs kan veroorzaken. Het diagram hieronder toont deze grote bol van modderarchitectuur.

Main gebruikt services a, B en C, die elk Util gebruiken. Service C maakt ook gebruik van Service A.

waarom Dependency Injection het helemaal verkeerd had

in een zoektocht naar een betere aanpak, kunnen we een techniek gebruiken die afhankelijkheid injectie wordt genoemd. Bij deze methode wordt ervan uitgegaan dat alle componenten via interfaces moeten worden gebruikt. Ik heb beweringen gelezen dat het elementen loskoppelt, maar is dat echt zo? Geen. Bekijk het diagram hieronder.

de vorige architectuur maar met afhankelijkheid injectie. Nu belangrijkste toepassingen Interface Service A, B, en C, die worden geïmplementeerd door hun overeenkomstige diensten. Services A en C gebruiken zowel Interface Service B als Interface Util, die door Util wordt geà mplementeerd. Service C maakt ook gebruik van Interface Service A. Elke service samen met zijn interface wordt beschouwd als een element.

het enige verschil tussen de huidige situatie en een grote modderbal is het feit dat we nu, in plaats van lessen direct te bellen, ze bellen via hun interfaces. Het verbetert nauwelijks scheidende elementen van elkaar. Als u bijvoorbeeld Service A in een ander project wilt hergebruiken, kunt u dat doen door Service A zelf te verwijderen, samen met Interface A, en ook Interface B en Interface Util. Zoals u kunt zien, is Service A nog steeds afhankelijk van andere elementen. Als gevolg daarvan krijgen we nog steeds problemen met het veranderen van code op de ene plaats en het verknoeien van gedrag op een andere. Het creëert nog steeds het probleem dat als u Service B en Interface B wijzigt, u alle elementen moet wijzigen die ervan afhankelijk zijn. Deze aanpak lost niets op; naar mijn mening voegt het gewoon een laag interface toe bovenop elementen. U moet nooit enige afhankelijkheden injecteren, maar in plaats daarvan moet u zich te ontdoen van hen voor eens en voor altijd. Hoera voor onafhankelijkheid!

de oplossing voor modulaire Code

de aanpak lost volgens mij alle hoofdpijnen van afhankelijkheden op. Je maakt een component en zijn luisteraar. Een luisteraar is een eenvoudige interface. Wanneer u een methode van buiten het huidige element moet aanroepen, voegt u eenvoudig een methode toe aan de luisteraar en roept deze in plaats daarvan aan. Het element mag alleen bestanden, aanroepmethoden binnen zijn pakket gebruiken, en klassen gebruiken die door main framework of andere gebruikte bibliotheken worden geleverd. Hieronder kunt u een diagram zien van de toepassing die is aangepast om elementarchitectuur te gebruiken.

een diagram van de toepassing aangepast om elementarchitectuur te gebruiken. Belangrijkste maakt gebruik van Util en alle drie de diensten. Main implementeert ook een luisteraar voor elke dienst, die door die dienst wordt gebruikt. Een luisteraar en service samen worden beschouwd als een element.

merk op dat in deze architectuur alleen de klasse Main meerdere afhankelijkheden heeft. Het sluit alle elementen samen en vat de zakelijke logica van de applicatie.

diensten daarentegen zijn volledig onafhankelijke elementen. Nu, u kunt elke service uit deze applicatie en hergebruik ze ergens anders. Ze zijn niet afhankelijk van iets anders. Maar wacht, het wordt nog beter: je hoeft die services nooit meer aan te passen, zolang je hun gedrag niet verandert. Zolang deze diensten doen wat ze moeten doen, kunnen ze onaangeroerd blijven tot het einde der tijden. Ze kunnen worden gemaakt door een professionele software engineer, of een eerste keer coder gecompromitteerd van de slechtste spaghetti code iemand ooit gekookt met goto statements gemengd in. Het maakt niet uit, want hun logica is ingekapseld. Hoe verschrikkelijk het ook is, het zal nooit naar andere klassen vloeien. Dat geeft je ook de mogelijkheid om het werk in een project te splitsen tussen meerdere ontwikkelaars, waar elke ontwikkelaar zelfstandig kan werken aan hun eigen component zonder de noodzaak om een ander te onderbreken of zelfs te weten over het bestaan van andere ontwikkelaars.

ten slotte kunt u nog een keer beginnen met het schrijven van onafhankelijke code, net als aan het begin van uw laatste project.

Elementpatroon

laten we het structurele elementpatroon definiëren zodat we het op een herhaalbare manier kunnen maken.

de eenvoudigste versie van het element bestaat uit twee dingen: Een hoofdelement klasse en een luisteraar. Als je een element wilt gebruiken, dan moet je de luisteraar implementeren en bellen naar de Hoofdklasse. Hier is een diagram van de eenvoudigste configuratie:

een diagram van een enkel element en zijn luisteraar binnen een app. Zoals voorheen, de App maakt gebruik van het element, die gebruik maakt van de luisteraar, die wordt geïmplementeerd door de App.

Uiteraard moet je uiteindelijk meer complexiteit toevoegen aan het element, maar je kunt dit gemakkelijk doen. Zorg er gewoon voor dat geen van uw logische klassen afhankelijk zijn van andere bestanden in het project. Ze kunnen alleen het belangrijkste framework, geïmporteerde bibliotheken en andere bestanden in dit element gebruiken. Als het gaat om asset-bestanden zoals afbeeldingen, weergaven, geluiden, enz., ze moeten ook worden ingekapseld in elementen, zodat ze in de toekomst gemakkelijk te hergebruiken zullen zijn. U kunt gewoon de hele map kopiëren naar een ander project en daar is het!

hieronder kunt u een voorbeeldgrafiek zien met een geavanceerder element. Merk op dat het bestaat uit een weergave die het gebruikt en het is niet afhankelijk van andere applicatie-bestanden. Als u een eenvoudige methode voor het controleren van afhankelijkheden wilt weten, kijk dan naar de sectie importeren. Zijn er bestanden van buiten het huidige element? Als dat zo is, dan moet je die afhankelijkheden verwijderen door ze naar het element te verplaatsen of door een passende aanroep toe te voegen aan de luisteraar.

een eenvoudig diagram van een complexer element. Hier bestaat de grotere betekenis van het woord "element" uit zes delen: View; Logica

laten we ook eens een kijkje nemen op een eenvoudige “Hello World” Voorbeeld gemaakt in Java.

public class Main { interface ElementListener { void printOutput(String message); } static class Element { private ElementListener listener; public Element(ElementListener listener) { this.listener = listener; } public void sayHello() { String message = "Hello World of Elements!"; this.listener.printOutput(message); } } static class App { public App() { } public void start() { // Build listener ElementListener elementListener = message -> System.out.println(message); // Assemble element Element element = new Element(elementListener); element.sayHello(); } } public static void main(String args) { App app = new App(); app.start(); }}

aanvankelijk definiëren we ElementListener om de methode te specificeren die de uitvoer afdrukt. Het element zelf wordt hieronder gedefinieerd. Bij het aanroepen van sayHello op het element, drukt het eenvoudig een bericht af met ElementListener. Merk op dat het element volledig onafhankelijk is van de toepassing van de printOutput – methode. Het kan worden afgedrukt in de console, een fysieke printer, of een mooie UI. Het element is niet afhankelijk van die implementatie. Door deze abstractie kan dit element gemakkelijk worden hergebruikt in verschillende toepassingen.

bekijk nu de Hoofdklasse App. Het implementeert de luisteraar en assembleert het element samen met concrete implementatie. Nu kunnen we het gebruiken.

u kunt dit voorbeeld ook in JavaScript uitvoeren

Elementarchitectuur

laten we eens kijken naar het gebruik van het elementpatroon in grootschalige toepassingen. Het is één ding om het te laten zien in een klein project—het is iets anders om het toe te passen op de echte wereld.

de structuur van een full-stack webtoepassing die ik graag gebruik ziet er als volgt uit:

src├── client│ ├── app│ └── elements│ └── server ├── app └── elements

in een broncodemap splitsen we in eerste instantie de client-en serverbestanden op. Het is een redelijke zaak om te doen, omdat ze draaien in twee verschillende omgevingen: de browser en de back-end server.

dan splitsen we de code in elke laag in mappen genaamd app en elementen. Elements bestaat uit mappen met onafhankelijke componenten, terwijl de App-Map alle elementen samendraait en alle zakelijke logica opslaat.

op die manier kunnen elementen worden hergebruikt tussen verschillende projecten, terwijl alle toepassingsspecifieke complexiteit wordt ingekapseld in één map en vaak wordt gereduceerd tot eenvoudige aanroepen van elementen.

Hands-on voorbeeld

geloven dat de praktijk altijd trump theorie, laten we eens een kijkje nemen op een real-life Voorbeeld gemaakt in Node.js en TypeScript.

real Life voorbeeld

het is een zeer eenvoudige webapplicatie die kan worden gebruikt als een startpunt voor meer geavanceerde oplossingen. Het volgt het element architectuur en het maakt gebruik van een uitgebreid structureel element patroon.

uit highlights kunt u zien dat de hoofdpagina is onderscheiden als een element. Deze pagina bevat een eigen weergave. Dus als je het bijvoorbeeld wilt hergebruiken, kun je gewoon de hele map kopiëren en in een ander project laten vallen. Sluit alles aan elkaar en je bent klaar.

het is een basisvoorbeeld dat laat zien dat u vandaag kunt beginnen met het introduceren van elementen in uw eigen toepassing. U kunt beginnen met het onderscheiden van onafhankelijke componenten en scheiden hun logica. Het maakt niet uit hoe rommelig de code die u momenteel werkt aan is.

Ontwikkel Sneller, Hergebruik Vaker!

ik hoop dat u met deze nieuwe set hulpmiddelen gemakkelijker code kunt ontwikkelen die beter te onderhouden is. Voordat u in het gebruik van het element patroon in de praktijk springen, laten we snel samenvatten alle belangrijke punten:

  • veel problemen in software gebeuren als gevolg van afhankelijkheden tussen meerdere componenten.

  • door op één plaats een verandering aan te brengen, kun je onvoorspelbaar gedrag ergens anders introduceren.

drie gemeenschappelijke architectonische benaderingen zijn:

  • de grote bal modder. Het is geweldig voor snelle ontwikkeling, maar niet zo geweldig voor stabiele productiedoeleinden.

  • afhankelijkheid injectie. Het is een half gebakken oplossing die je moet vermijden.

  • Element architectuur. Deze oplossing stelt u in staat om onafhankelijke componenten te maken en ze te hergebruiken in andere projecten. Het is onderhoudbaar en briljant voor stabiele productie releases.

het basiselement patroon bestaat uit een Hoofdklasse die alle belangrijke methoden heeft, evenals een luisteraar die een eenvoudige interface is die communicatie met de buitenwereld mogelijk maakt.

om de architectuur van het volledige stack-element te bereiken, scheidt u eerst uw front-end van de back-end code. Vervolgens Maak je een map in elk voor een app en elementen. De map elements bestaat uit alle onafhankelijke elementen, terwijl de map app alles samendraait.

nu kunt u beginnen met het maken en delen van uw eigen elementen. Op de lange termijn, het zal u helpen gemakkelijk onderhoudbare producten te creëren. Veel succes en laat me weten wat je hebt gemaakt!

als u uw code voortijdig optimaliseert, lees dan hoe u de vloek van voortijdige optimalisatie door collega Toptaler Kevin Bloch kunt vermijden.

Geef een antwoord

Het e-mailadres wordt niet gepubliceerd.