oprettelse af virkelig modulær kode uden afhængigheder

udvikling af programmer er fantastisk, men… jeg tror, vi alle kan være enige om, at det kan være lidt af en følelsesmæssig rutsjebane. I begyndelsen er alt godt. Du tilføjer nye funktioner efter hinanden i et spørgsmål om dage, hvis ikke timer. Du er i gang!

hurtigt frem et par måneder, og din udviklingshastighed falder. Er det fordi du ikke arbejder så hårdt som før? Ikke rigtig. Lad os hurtigt frem et par måneder til, og din udviklingshastighed falder yderligere. At arbejde på dette projekt er ikke sjovt længere og er blevet et træk.

det bliver værre. Du begynder at opdage flere fejl i din ansøgning. Ofte skaber løsning af en fejl to nye. På dette tidspunkt kan du begynde at synge:

99 små fejl i koden.99 små bugs.Tag en ned, lappe den rundt,

…127 små fejl i koden.

Hvordan har du det med at arbejde på dette projekt nu? Hvis du er som mig, begynder du sandsynligvis at miste din motivation. Det er bare en smerte at udvikle denne applikation, da enhver ændring af eksisterende kode kan have uforudsigelige konsekvenser.

denne oplevelse er almindelig i programmelverdenen og kan forklare, hvorfor så mange programmører ønsker at smide deres kildekode væk og omskrive alt.

årsager til, at Programmeludviklingen går langsommere over tid

så hvad er årsagen til dette problem?

hovedårsagen er stigende kompleksitet. Fra min erfaring er den største bidragyder til den samlede kompleksitet det faktum, at i langt de fleste programmelprojekter er alt forbundet. På grund af de afhængigheder, som hver klasse har, hvis du ændrer en kode i klassen, der sender e-mails, kan dine brugere pludselig ikke registrere sig. Hvorfor det? Fordi din registreringskode afhænger af den kode, der sender e-mails. Nu Kan du ikke ændre noget uden at introducere fejl. Det er simpelthen ikke muligt at spore alle afhængigheder.

så der har du det; den virkelige årsag til vores problemer er at øge kompleksiteten fra alle de afhængigheder, som vores kode har.

stor kugle af mudder og hvordan man reducerer det

sjov ting er, dette problem har været kendt i årevis nu. Det er et almindeligt anti-mønster kaldet “big Ball of mud.”Jeg har set den type arkitektur i næsten alle projekter, jeg har arbejdet med gennem årene i flere forskellige virksomheder.

så hvad er dette anti-mønster nøjagtigt? Kort sagt, Du får en stor kugle af mudder, når hvert element har en afhængighed med andre elementer. Nedenfor kan du se en graf over afhængighederne fra det velkendte open source-projekt Apache Hadoop. For at visualisere den store mudderkugle (eller rettere den store garnkugle) tegner du en cirkel og placerer klasser fra projektet jævnt på det. Træk bare en linje mellem hvert par klasser, der afhænger af hinanden. Nu Kan du se kilden til dine problemer.

en visualisering af Apache Hadoops store mudderkugle med et par dusin noder og hundreder af linjer, der forbinder dem med hinanden.

Apache Hadoop ‘ s “big ball of mud”

en løsning med modulær kode

så jeg stillede mig selv et spørgsmål: ville det være muligt at reducere kompleksiteten og stadig have det sjovt som i starten af projektet? For at være ærlig kan du ikke fjerne al kompleksiteten. Hvis du vil tilføje nye funktioner, skal du altid hæve kodekompleksiteten. Ikke desto mindre kan kompleksitet flyttes og adskilles.

hvordan andre industrier løser dette Problem

tænk på den mekaniske industri. Når en lille mekanisk butik opretter maskiner, køber de et sæt standardelementer, opretter et par brugerdefinerede og sætter dem sammen. De kan fremstille disse komponenter helt separat og samle alt i slutningen, hvilket kun gør et par justeringer. Hvordan er det muligt? De ved, hvordan hvert element vil passe sammen ved at sætte industristandarder som bolte størrelser, og up-front beslutninger som størrelsen af monteringshuller og afstanden mellem dem.

et teknisk diagram over en fysisk mekanisme og hvordan dens stykker passer sammen. Stykkerne er nummereret i rækkefølge, som de skal vedhæftes næste, men den rækkefølge fra venstre mod højre går 5, 3, 4, 1, 2.

hvert element i samlingen ovenfor kan leveres af et separat firma, der overhovedet ikke har nogen viden om det endelige produkt eller dets andre stykker. Så længe hvert modulelement er fremstillet i henhold til specifikationerne, vil du være i stand til at oprette den endelige enhed som planlagt.

kan vi gentage det i programmelindustrien?

sikker på, at vi kan! Ved at bruge grænseflader og inversion af kontrolprincippet; den bedste del er, at denne tilgang kan bruges på ethvert objektorienteret sprog: Java, C#, Hurtig, TypeScript, JavaScript, PHP-listen fortsætter og fortsætter. Du har ikke brug for nogen fancy ramme for at anvende denne metode. Du skal bare holde dig til et par enkle regler og forblive disciplineret.

Inversion af kontrol er din ven

da jeg først hørte om inversion af kontrol, indså jeg straks, at jeg havde fundet en løsning. Det er et begreb om at tage eksisterende afhængigheder og invertere dem ved hjælp af grænseflader. Grænseflader er enkle erklæringer om metoder. De giver ikke nogen konkret implementering. Som følge heraf kan de bruges som en aftale mellem to elementer om, hvordan man forbinder dem. De kan bruges som modulære stik, hvis du vil. Så længe et element giver grænsefladen, og et andet element giver implementeringen for det, kan de arbejde sammen uden at vide noget om hinanden. Det er genialt.

lad os se på et simpelt eksempel, hvordan kan vi afkoble vores system for at oprette modulær kode. Diagrammerne nedenfor er implementeret som enkle Java-applikationer. Du kan finde dem på dette GitHub-arkiv.

Problem

lad os antage, at vi har en meget enkel applikation, der kun består af en Main klasse, tre tjenester og en enkelt Util klasse. Disse elementer afhænger af hinanden på flere måder. Nedenfor kan du se en implementering ved hjælp af “big ball of mud” – tilgangen. Klasser kalder simpelthen hinanden. De er tæt koblet, og du kan ikke bare tage et element ud uden at røre ved andre. Applikationer oprettet ved hjælp af denne stil giver dig mulighed for i første omgang at vokse hurtigt. Jeg tror, at denne stil er passende til proof-of-concept-projekter, da du nemt kan lege med tingene. Ikke desto mindre er det ikke passende til produktionsklare løsninger, fordi selv vedligeholdelse kan være farlig, og enhver enkelt ændring kan skabe uforudsigelige fejl. Diagrammet nedenfor viser denne store kugle af mudderarkitektur.

Main bruger tjenester A, B og C, som hver bruger Util. Service C bruger også Service A.

hvorfor Afhængighedsinjektion fik det hele forkert

i en søgning efter en bedre tilgang kan vi bruge en teknik kaldet afhængighedsinjektion. Denne metode forudsætter, at alle komponenter skal bruges via grænseflader. Jeg har læst påstande om, at det afkobler elementer, men gør det virkelig? Ingen. Tag et kig på diagrammet nedenfor.

den tidligere arkitektur, men med afhængighedsinjektion. Nu bruger Main Interface Service A, B og C, som implementeres af deres tilsvarende tjenester. Tjenester A og C bruger begge Interface Service B og Interface Util, som implementeres af Util. Service C bruger også Interface Service A. hver tjeneste sammen med dens grænseflade anses for at være et element.

den eneste forskel mellem den nuværende situation og en stor mudderkugle er det faktum, at vi nu i stedet for at kalde klasser direkte kalder dem gennem deres grænseflader. Det forbedrer lidt at adskille elementer fra hinanden. Hvis du for eksempel gerne vil genbruge Service A i et andet projekt, kan du gøre det ved at tage ud Service A selv sammen med Interface A samt Interface B og Interface Util. Som du kan se, Service A afhænger stadig af andre elementer. Som et resultat får vi stadig problemer med at ændre kode et sted og ødelægge adfærd på et andet. Det skaber stadig problemet, at hvis du ændrer Service B og Interface B, skal du ændre alle elementer, der afhænger af det. Denne tilgang løser ikke noget; efter min mening tilføjer det bare et lag af interface oven på elementer. Du bør aldrig injicere afhængigheder, men i stedet skal du slippe af med dem en gang for alle. Hurra for uafhængighed!

løsningen til modulær kode

den tilgang, jeg tror løser alle de vigtigste hovedpine i afhængigheder, gør det ved slet ikke at bruge afhængigheder. Du opretter en komponent og dens lytter. En lytter er en simpel grænseflade. Når du har brug for at kalde en metode uden for det aktuelle element, tilføjer du blot en metode til lytteren og kalder den i stedet. Elementet må kun bruge filer, opkaldsmetoder i sin pakke og bruge klasser leveret af hovedrammer eller andre brugte biblioteker. Nedenfor kan du se et diagram over applikationen, der er ændret til at bruge elementarkitektur.

et diagram over applikationen ændret til at bruge elementarkitektur. Main bruger Util og alle tre tjenester. Main implementerer også en lytter til hver tjeneste, som bruges af denne tjeneste. En lytter og service sammen betragtes som et element.

bemærk, at i denne arkitektur er det kun Main – klassen, der har flere afhængigheder. Det ledninger alle elementer sammen og indkapsler programmets forretningslogik.

tjenester er derimod helt uafhængige elementer. Nu Kan du tage hver tjeneste ud af denne applikation og genbruge dem et andet sted. De er ikke afhængige af noget andet. Men vent, det bliver bedre: du behøver ikke at ændre disse tjenester nogensinde igen, så længe du ikke ændrer deres adfærd. Så længe disse tjenester gør, hvad de skulle gøre, de kan efterlades uberørt indtil slutningen af tiden. De kan oprettes af en professionel programmel ingeniør, eller en første gang coder kompromitteret af den værste spaghetti kode nogen nogensinde kogt med goto udsagn blandet i. Det betyder ikke noget, fordi deres logik er indkapslet. Så forfærdeligt som det kan være, vil det aldrig spildes ud til andre klasser. Det giver dig også mulighed for at opdele arbejde i et projekt mellem flere udviklere, hvor hver udvikler kan arbejde på deres egen komponent uafhængigt uden at skulle afbryde en anden eller endda vide om eksistensen af andre udviklere.

endelig kan du begynde at skrive uafhængig kode endnu en gang, ligesom i begyndelsen af dit sidste projekt.

Elementmønster

lad os definere det strukturelle elementmønster, så vi kan oprette det på en gentagelig måde.

den enkleste version af elementet består af to ting: Et vigtigt element klasse og en lytter. Hvis du vil bruge et element, skal du implementere lytteren og foretage opkald til hovedklassen. Her er et diagram over den enkleste konfiguration:

et diagram over et enkelt element og dets lytter i en app. Som før bruger appen elementet, der bruger sin lytter, som implementeres af appen.

det er klart, at du bliver nødt til at tilføje mere kompleksitet i elementet til sidst, men du kan gøre det let. Bare sørg for, at ingen af dine logiske klasser afhænger af andre filer i projektet. De kan kun bruge hovedrammen, importerede biblioteker og andre filer i dette element. Når det kommer til aktivfiler som billeder, visninger, lyde osv., de skal også indkapsles i elementer, så de i fremtiden vil være lette at genbruge. Du kan blot kopiere hele mappen til et andet projekt, og der er det!

nedenfor kan du se en eksempelgraf, der viser et mere avanceret element. Bemærk, at den består af en visning, som den bruger, og den afhænger ikke af andre applikationsfiler. Hvis du vil vide en enkel metode til kontrol af afhængigheder, skal du bare se på afsnittet import. Er der nogen filer uden for det aktuelle element? I så fald skal du fjerne disse afhængigheder ved enten at flytte dem ind i elementet eller ved at tilføje et passende opkald til lytteren.

et simpelt diagram over et mere komplekst element. Her består den større betydning af ordet "element" af seks dele: udsigt; Logik A, B og C; Element; og Element lytter. Forholdet mellem de to sidstnævnte og Appen er de samme som før, men det indre Element bruger også Logik A og C. logik C bruger Logik A og B. Logik A bruger logik B og visning.

lad os også se på et simpelt “Hej Verden” eksempel oprettet i 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(); }}

oprindeligt definerer vi ElementListener for at specificere den metode, der udskriver output. Selve elementet er defineret nedenfor. Når du ringer sayHello på elementet, udskriver den simpelthen en besked ved hjælp af ElementListener. Bemærk, at elementet er helt uafhængigt af implementeringen af printOutput – metoden. Det kan udskrives i konsollen, en fysisk printer eller en fancy brugergrænseflade. Elementet afhænger ikke af denne implementering. På grund af denne abstraktion kan dette element let genbruges i forskellige applikationer.

nu har et kig på de vigtigste App klasse. Det implementerer lytteren og samler elementet sammen med konkret implementering. Nu kan vi begynde at bruge det.

du kan også køre dette eksempel i JavaScript her

Elementarkitektur

lad os se på at bruge elementmønsteret i store applikationer. Det er en ting at vise det i et lille projekt—det er en anden at anvende det på den virkelige verden.

strukturen af en full-stack-applikation, som jeg kan lide at bruge, ser ud som følger:

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

i en kildekodemappe opdeler vi oprindeligt klient-og serverfilerne. Det er en rimelig ting at gøre, da de kører i to forskellige miljøer: bro.sereren og back-end-serveren.

så deler vi koden i hvert lag i mapper kaldet app og elementer. Elements består af mapper med uafhængige komponenter, mens app-mappen leder alle elementerne sammen og gemmer al forretningslogik.

på den måde kan elementer genbruges mellem forskellige projekter, mens Al applikationsspecifik kompleksitet er indkapslet i en enkelt mappe og ofte reduceres til enkle opkald til elementer.

Hands-on eksempel

tro på, at praksis altid trumfer teori, lad os se på et virkeligt eksempel oprettet i Node.js og TypeScript.

Real Life eksempel

det er et meget simpelt internetprogram, der kan bruges som udgangspunkt for mere avancerede løsninger. Det følger elementarkitekturen såvel som det bruger et omfattende strukturelt elementmønster.

fra højdepunkter kan du se, at hovedsiden er blevet skelnet som et element. Denne side indeholder sin egen visning. Så når du for eksempel vil genbruge det, kan du blot kopiere hele mappen og slippe den i et andet projekt. Bare sæt alt sammen, så er du klar.

det er et grundlæggende eksempel, der viser, at du kan begynde at introducere elementer i din egen applikation i dag. Du kan begynde at skelne mellem uafhængige komponenter og adskille deres logik. Det betyder ikke noget, hvor rodet den kode, du i øjeblikket arbejder på, er.

Udvikle Hurtigere, Genbruge Oftere!

jeg håber, at du med dette nye sæt værktøjer lettere kan udvikle kode, der er mere vedligeholdelig. Før du hopper ind i at bruge elementmønsteret i praksis, lad os hurtigt sammenfatte alle hovedpunkterne:

  • mange problemer opstår på grund af afhængigheder mellem flere komponenter.

  • ved at foretage en ændring et sted kan du introducere uforudsigelig adfærd et andet sted.

tre fælles arkitektoniske tilgange er:

  • den store kugle af mudder. Det er fantastisk til hurtig udvikling, men ikke så stor til stabile produktionsformål.

  • afhængighed injektion. Det er en halvbagt løsning, som du bør undgå.

  • Element arkitektur. Denne løsning giver dig mulighed for at oprette uafhængige komponenter og genbruge dem i andre projekter. Det er vedligeholdeligt og strålende til stabile produktionsudgivelser.

det grundlæggende element mønster består af en hovedklasse, der har alle de store metoder samt en lytter, der er en simpel grænseflade, der giver mulighed for kommunikation med den eksterne verden.

for at opnå fuld stack elementarkitektur skal du først adskille din front-end fra back-end-koden. Derefter opretter du en mappe i hver for en app og elementer. Elements-mappen består af alle uafhængige elementer, mens app-mappen leder alt sammen.

nu Kan du begynde at oprette og dele dine egne elementer. I det lange løb vil det hjælpe dig med at oprette let vedligeholdelige produkter. Held og lykke og lad mig vide, hvad du skabte!

hvis du finder dig selv for tidligt at optimere din kode, skal du læse, hvordan du undgår forbandelsen af for tidlig optimering af kollega Toptaler Kevin Bloch.

Skriv et svar

Din e-mailadresse vil ikke blive publiceret.