Opprette Virkelig Modulær Kode Uten Avhengigheter
Utvikle programvare er flott, men … I begynnelsen er alt bra. Du legger til nye funksjoner etter hverandre i løpet av dager om ikke timer. Du er på rulle!
Spol fremover noen måneder, og utviklingshastigheten minker. Er det fordi du ikke jobber like hardt som før? Ikke egentlig. La oss spole fremover noen få måneder, og utviklingshastigheten din faller videre. Å jobbe med dette prosjektet er ikke morsomt lenger og har blitt en dra.
Det blir verre. Du begynner å oppdage flere feil i søknaden din. Ofte løser en feil to nye. På dette punktet kan du begynne å synge:
99 små bugs i koden.99 små bugs.Ta en ned, lappe den rundt,
… 127 små bugs i koden.
hva synes du om å jobbe med dette prosjektet nå? Hvis du er som meg, begynner du sannsynligvis å miste motivasjonen. Det er bare en smerte å utvikle dette programmet, siden hver endring i eksisterende kode kan ha uforutsigbare konsekvenser.
denne erfaringen er vanlig i programvareverdenen og kan forklare hvorfor så mange programmerere vil kaste kildekoden bort og omskrive alt.
- Grunner Til At Programvareutvikling Bremser over Tid
- Stor Ball Av Gjørme Og Hvordan Å Redusere Den
- En Løsning Med Modulær Kode
- Hvordan Andre Næringer Løser Dette Problemet
- Inversjon Av Kontroll Er Din Venn
- Problem
- Hvorfor Dependency Injection Fikk Alt Galt
- Løsningen For Modulær Kode
- Elementmønster
- Elementarkitektur
- Hands-On Eksempel
- Utvikle Raskere, Gjenbruk Oftere!
Grunner Til At Programvareutvikling Bremser over Tid
Så hva er årsaken til dette problemet?
hovedårsaken er økende kompleksitet. Fra min erfaring er den største bidragsyteren til total kompleksitet det faktum at i de aller fleste programvareprosjekter er alt koblet sammen. På grunn av avhengighetene som hver klasse har, hvis du endrer noen kode i klassen som sender e-post, kan brukerne plutselig ikke registrere seg. Hvorfor det? Fordi registreringskoden din avhenger av koden som sender e-post. Nå kan du ikke endre noe uten å introdusere feil. Det er rett og slett ikke mulig å spore alle avhengigheter.
så der har du det; den virkelige årsaken til våre problemer er å øke kompleksiteten som kommer fra alle avhengighetene som koden vår har.
Stor Ball Av Gjørme Og Hvordan Å Redusere Den
Morsom ting er, dette problemet har vært kjent i mange år nå. Det er en vanlig anti-mønster kalt ” big ball of mud.”Jeg har sett den typen arkitektur i nesten alle prosjekter jeg jobbet med gjennom årene i flere forskjellige selskaper.
så hva er dette anti-mønsteret nøyaktig? Enkelt sagt får du en stor ball av gjørme når hvert element har en avhengighet med andre elementer. Nedenfor kan du se en graf over avhengighetene Fra kjente open source-prosjekt Apache Hadoop. For å visualisere den store ballen av gjørme (eller rettere den store ballen av garn) tegner du en sirkel og legger klasser fra prosjektet jevnt på den. Bare tegne en linje mellom hvert par klasser som er avhengige av hverandre. Nå kan du se kilden til dine problemer.
En Løsning Med Modulær Kode
så jeg spurte meg selv et spørsmål: Ville det være mulig å redusere kompleksiteten og fortsatt ha det gøy som i begynnelsen av prosjektet? Sannheten blir fortalt, du kan ikke eliminere all kompleksiteten. Hvis du vil legge til nye funksjoner, må du alltid øke kodekompleksiteten. Kompleksiteten kan flyttes og separeres.
Hvordan Andre Næringer Løser Dette Problemet
Tenk på mekanisk industri. Når noen små mekaniske butikker lager maskiner, kjøper de et sett med standardelementer, lager noen få tilpassede, og legger dem sammen. De kan lage disse komponentene helt separat og montere alt på slutten, og gjør bare noen få tweaks. Hvordan er dette mulig? De vet hvordan hvert element vil passe sammen ved å sette industristandarder som bolter størrelser, og up-front beslutninger som størrelsen på monteringshull og avstanden mellom dem.
hvert element i forsamlingen ovenfor kan leveres av et eget selskap som ikke har noen kunnskap om sluttproduktet eller dets andre stykker. Så lenge hvert modulært element er produsert i henhold til spesifikasjoner, vil du kunne lage den endelige enheten som planlagt.
Kan vi gjenskape det i programvareindustrien?
Klart vi kan! Ved å bruke grensesnitt og inversjon av kontrollprinsippet; det beste er at denne tilnærmingen kan brukes i alle objektorienterte språk: Java, C#, Swift, TypeScript, JavaScript, PHP—listen fortsetter og fortsetter. Du trenger ikke noe fancy rammeverk for å bruke denne metoden. Du trenger bare å holde deg til noen enkle regler og holde deg disiplinert.
Inversjon Av Kontroll Er Din Venn
da jeg først hørte om inversjon av kontroll, innså jeg umiddelbart at jeg hadde funnet en løsning. Det er et konsept for å ta eksisterende avhengigheter og invertere dem ved hjelp av grensesnitt. Grensesnitt er enkle deklarasjoner av metoder. De gir ingen konkret implementering. Som et resultat kan de brukes som en avtale mellom to elementer om hvordan de skal kobles til. De kan brukes som modulære kontakter, hvis du vil. Så lenge ett element gir grensesnittet og et annet element gir implementeringen for det, kan de jobbe sammen uten å vite noe om hverandre. Det er genialt.
La oss se på et enkelt eksempel hvordan kan vi avkoble systemet vårt for å lage modulær kode. Diagrammene nedenfor er implementert som enkle Java-applikasjoner. Du finner dem på Dette GitHub-depotet.
Problem
la oss anta at vi har en veldig enkel applikasjon som bare består av en Main
klasse, tre tjenester og en enkelt Util
klasse. Disse elementene er avhengige av hverandre på flere måter. Nedenfor kan du se en implementering ved hjelp av “big ball of mud” – tilnærmingen. Klasser kaller bare hverandre. De er tett koblet, og du kan ikke bare ta ut ett element uten å berøre andre. Programmer opprettet ved hjelp av denne stilen lar deg i utgangspunktet vokse raskt. Jeg tror denne stilen passer for proof-of-concept prosjekter siden du kan leke med ting enkelt. Likevel er det ikke egnet for produksjonsklare løsninger fordi selv vedlikehold kan være farlig, og enhver enkelt endring kan skape uforutsigbare feil. Diagrammet nedenfor viser denne store ballen av gjørme arkitektur.
Hvorfor Dependency Injection Fikk Alt Galt
i et søk etter en bedre tilnærming kan vi bruke en teknikk som kalles dependency injection. Denne metoden antar at alle komponenter skal brukes gjennom grensesnitt. Jeg har lest påstander om at det avkobler elementer, men gjør det egentlig, skjønt? Ingen. Ta en titt på diagrammet nedenfor.
den eneste forskjellen mellom den nåværende situasjonen og en stor ball av gjørme er det faktum at nå, i stedet for å ringe klasser direkte, kaller vi dem gjennom deres grensesnitt. Det forbedrer litt separerende elementer fra hverandre. Hvis du for eksempel vil gjenbruke Service A
i et annet prosjekt, kan du gjøre det ved å ta ut Service A
selv, sammen med Interface A
, samt Interface B
og Interface Util
. Som du kan se, er Service A
fortsatt avhengig av andre elementer. Som et resultat får vi fortsatt problemer med å endre kode på ett sted og rote opp atferd i en annen. Det skaper fortsatt problemet at hvis du endrer Service B
og Interface B
, må du endre alle elementer som er avhengige av det. Denne tilnærmingen løser ikke noe; etter min mening legger det bare et lag med grensesnitt på toppen av elementer. Du bør aldri injisere noen avhengigheter, men i stedet bør du bli kvitt dem en gang for alle. Hurra for uavhengighet!
Løsningen For Modulær Kode
tilnærmingen jeg tror løser alle hovedhodepine av avhengigheter, gjør det ved ikke å bruke avhengigheter i det hele tatt. Du lager en komponent og dens lytter. En lytter er et enkelt grensesnitt. Når du trenger å ringe en metode fra utenfor det nåværende elementet, legger du bare til en metode for lytteren og kaller den i stedet. Elementet er bare tillatt å bruke filer, ringe metoder i sin pakke, og bruke klasser levert av hoved rammeverk eller andre brukte biblioteker. Nedenfor kan du se et diagram over programmet endret for å bruke elementarkitektur.
Vær oppmerksom på at i denne arkitekturen har bare klassen Main
flere avhengigheter. Det ledninger alle elementer sammen og innkapsler programmets forretningslogikk.
Tjenester, derimot, er helt uavhengige elementer. Nå kan du ta ut hver tjeneste ut av dette programmet og bruke dem et annet sted. De er ikke avhengige av noe annet. Men vent, det blir bedre: Du trenger ikke å endre disse tjenestene igjen, så lenge du ikke endrer deres oppførsel. Så lenge disse tjenestene gjør det de skal gjøre, kan de stå uberørt til slutten av tiden. De kan være laget av en profesjonell programvare ingeniør, eller en første gang koder kompromittert av den verste spaghetti kode noen gang kokt med goto
uttalelser blandet inn. Det spiller ingen rolle, fordi deres logikk er innkapslet. Så fryktelig som det kan være, vil det aldri spyle ut til andre klasser. Det gir deg også muligheten til å dele arbeid i et prosjekt mellom flere utviklere, hvor hver utvikler kan jobbe på egen komponent uavhengig uten å måtte forstyrre en annen eller til og med vite om eksistensen av andre utviklere.
Til Slutt kan du begynne å skrive uavhengig kode en gang til, akkurat som i begynnelsen av ditt siste prosjekt.
Elementmønster
la oss definere det strukturelle elementmønsteret slik at vi kan lage det på en repeterbar måte.
den enkleste versjonen av elementet består av to ting: Et hovedelement klasse og en lytter. Hvis du vil bruke et element, må du implementere lytteren og ringe til hovedklassen. Her er et diagram over den enkleste konfigurasjonen:
Åpenbart må du legge til mer kompleksitet i elementet til slutt, men du kan gjøre det enkelt. Bare vær sikker på at ingen av logikklassene dine er avhengige av andre filer i prosjektet. De kan bare bruke hovedrammen, importerte biblioteker og andre filer i dette elementet. Når det gjelder aktivafiler som bilder, visninger, lyder, etc., de bør også innkapsles i elementer slik at de i fremtiden vil være enkle å gjenbruke. Du kan bare kopiere hele mappen til et annet prosjekt, og det er det!
Nedenfor kan du se et eksempeldiagram som viser et mer avansert element. Legg merke til at den består av en visning som den bruker, og den er ikke avhengig av andre programfiler. Hvis du vil vite en enkel metode for å sjekke avhengigheter, bare se på import-delen. Er det noen filer fra utenfor det nåværende elementet? I så fall må du fjerne disse avhengighetene ved å enten flytte dem inn i elementet eller ved å legge til et passende anrop til lytteren.
La oss også se på et enkelt “Hello World” – eksempel opprettet 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(); }}
i Utgangspunktet definerer vi ElementListener
for å angi metoden som skriver ut utdata. Selve elementet er definert nedenfor. Ved å ringe sayHello
på elementet, skriver det bare ut en melding ved hjelp av ElementListener
. Legg merke til at elementet er helt uavhengig av implementeringen av printOutput
– metoden. Den kan skrives ut i konsollen, en fysisk skriver, eller en fancy UI. Elementet er ikke avhengig av den implementeringen. På grunn av denne abstraksjonen kan dette elementet enkelt gjenbrukes i forskjellige applikasjoner.
se Nå på hovedklassen App
. Den implementerer lytteren og monterer elementet sammen med konkret implementering. Nå kan vi begynne å bruke den.
du kan også kjøre dette eksemplet I JavaScript her
Elementarkitektur
La Oss se på å bruke elementmønsteret i store applikasjoner. Det er en ting å vise det i et lite prosjekt-det er en annen å bruke den til den virkelige verden.
strukturen til en full-stack webapplikasjon som jeg liker å bruke, ser ut som følger:
src├── client│ ├── app│ └── elements│ └── server ├── app └── elements
i en kildekodemappe deler vi først klient-og serverfilene. Det er en rimelig ting å gjøre, siden de kjører i to forskjellige miljøer: nettleseren og back-end-serveren.
så deler vi koden i hvert lag i mapper kalt app og elements. Elements består av mapper med uavhengige komponenter, mens app-mappen kobler alle elementene sammen og lagrer all forretningslogikk.
på den måten kan elementer gjenbrukes mellom ulike prosjekter, mens all applikasjonsspesifikk kompleksitet er innkapslet i en enkelt mappe og ganske ofte redusert til enkle anrop til elementer.
Hands-On Eksempel
Å Tro at praksis alltid trumfer teori, la oss se på et virkelighetseksempel opprettet I Node.Js Og TypeScript.
Real Life Eksempel
Det er en veldig enkel web-applikasjon som kan brukes som utgangspunkt for mer avanserte løsninger. Den følger elementarkitekturen, så vel som den bruker et omfattende strukturelt elementmønster.
fra høydepunkter kan du se at hovedsiden har blitt skilt ut som et element. Denne siden inneholder sin egen visning. Så når du for eksempel vil bruke den på nytt, kan du bare kopiere hele mappen og slippe den inn i et annet prosjekt. Bare koble alt sammen og du er klar.
det er et grunnleggende eksempel som viser at du kan begynne å introdusere elementer i din egen søknad i dag. Du kan begynne å skille mellom uavhengige komponenter og skille deres logikk. Det spiller ingen rolle hvor rotete koden du jobber med er.
Utvikle Raskere, Gjenbruk Oftere!
jeg håper at med dette nye settet med verktøy vil du lettere kunne utvikle kode som er mer vedlikeholdbar. Før du hopper inn i å bruke elementmønsteret i praksis, la oss raskt oppsummere alle hovedpoengene:
-
mange problemer i programvare skje på grunn av avhengigheter mellom flere komponenter.
-
ved å gjøre en endring på ett sted, kan du introdusere uforutsigbar oppførsel et annet sted.
Tre vanlige arkitektoniske tilnærminger er:
-
den store ballen av gjørme. Det er flott for rask utvikling, men ikke så bra for stabile produksjonsformål.
-
Avhengighet injeksjon. Det er en halvbakt løsning som du bør unngå.
-
Element arkitektur. Denne løsningen lar deg lage uavhengige komponenter og gjenbruke dem i andre prosjekter. Det er vedlikeholdsbar og strålende for stabile produksjonsutgivelser.
det grunnleggende elementmønsteret består av en hovedklasse som har alle de store metodene, samt en lytter som er et enkelt grensesnitt som muliggjør kommunikasjon med den eksterne verden.
for å oppnå full-stack element arkitektur, først du skille front-end fra back-end kode. Deretter oppretter du en mappe i hver for en app og elementer. Elements-mappen består av alle uavhengige elementer, mens app-mappen ledninger alt sammen.
nå kan du begynne å lage og dele dine egne elementer. I det lange løp vil det hjelpe deg med å lage lett vedlikeholdbare produkter. Lykke til og la meg vite hva du opprettet!
også, hvis du finner deg selv for tidlig å optimalisere koden din, les Hvordan Du Unngår Forbannelsen Av For Tidlig Optimalisering Av andre Toptaler Kevin Bloch.