crearea unui cod Modular cu adevărat fără dependențe

dezvoltarea software-ului este grozavă, dar… cred că putem fi cu toții de acord că poate fi un pic de rollercoaster emoțional. La început, totul este minunat. Adăugați noi caracteristici unul după altul într-o chestiune de zile, dacă nu ore. Ești pe val!

Fast forward câteva luni, și viteza de dezvoltare scade. Este pentru că nu lucrați la fel de greu ca înainte? Nu chiar. Să derulăm înainte încă câteva luni, iar viteza de dezvoltare scade și mai mult. Lucrul la acest proiect nu mai este distractiv și a devenit un drag.

se înrăutățește. Începeți să descoperiți mai multe bug-uri în aplicația dvs. Adesea, rezolvarea unei erori creează două noi. În acest moment, puteți începe să cântați:

99 bug – uri mici în cod.99 bug-uri mici.Ia – o jos, patch-l în jurul,

… 127 bug-uri mici în codul.

ce părere aveți despre lucrul la acest Proiect acum? Dacă ești ca mine, probabil că începi să-ți pierzi motivația. Este doar o durere pentru a dezvolta această aplicație, deoarece fiecare modificare a codului existent poate avea consecințe imprevizibile.

această experiență este comună în lumea software-ului și poate explica de ce atât de mulți programatori doresc să-și arunce codul sursă și să rescrie totul.

motive pentru care dezvoltarea de Software încetinește în timp

deci, care este motivul acestei probleme?

principala cauză este creșterea complexității. Din experiența mea, cel mai mare contribuitor la complexitatea generală este faptul că, în marea majoritate a proiectelor software, totul este conectat. Din cauza dependențelor pe care le are fiecare clasă, dacă schimbați un cod din clasa care trimite e-mailuri, utilizatorii dvs. nu se pot înregistra brusc. De ce este asta? Deoarece codul dvs. de înregistrare depinde de codul care trimite e-mailuri. Acum nu puteți schimba nimic fără a introduce bug-uri. Pur și simplu nu este posibil să urmăriți toate dependențele.

deci iată-l; adevărata cauză a problemelor noastre este creșterea complexității provenind din toate dependențele pe care le are codul nostru.

minge mare de noroi și cum să-l reducă

lucru amuzant este, această problemă a fost cunoscut de ani de zile acum. Este un anti – model comun numit ” minge mare de noroi.”Am văzut acest tip de arhitectură în aproape toate proiectele la care am lucrat de-a lungul anilor în mai multe companii diferite.

deci, ce este acest anti-model exact? Pur și simplu vorbind, veți obține o minge mare de noroi atunci când fiecare element are o dependență cu alte elemente. Mai jos, puteți vedea un grafic al dependențelor din binecunoscutul proiect open-source Apache Hadoop. Pentru a vizualiza mingea mare de noroi (sau mai bine zis, mingea mare de fire), desenați un cerc și plasați clase din proiect în mod egal pe el. Doar trageți o linie între fiecare pereche de clase care depind una de cealaltă. Acum Puteți vedea sursa problemelor dvs.

o vizualizare a mingii mari de noroi a lui Apache Hadoop, cu câteva zeci de noduri și sute de linii care le leagă între ele.

Apache Hadoop “Big ball of mud”

o soluție cu cod Modular

așa că mi-am pus o întrebare: ar fi posibil să reducem complexitatea și să ne distrăm în continuare ca la începutul proiectului? Adevărul este că nu poți elimina toată complexitatea. Dacă doriți să adăugați funcții noi, va trebui întotdeauna să ridicați complexitatea codului. Cu toate acestea, complexitatea poate fi mutată și separată.

cum alte industrii rezolvă această problemă

gândiți-vă la industria mecanică. Când un mic magazin mecanic creează mașini, cumpără un set de elemente standard, creează câteva personalizate și le pun împreună. Ele pot face aceste componente complet separat și pot asambla totul la sfârșit, făcând doar câteva modificări. Cum este posibil acest lucru? Ei știu cum fiecare element se va potrivi împreună prin standarde industriale stabilite, cum ar fi dimensiunile șuruburilor și deciziile în față, cum ar fi dimensiunea găurilor de montare și distanța dintre ele.

o diagramă tehnică a unui mecanism fizic și modul în care piesele sale se potrivesc împreună. Piesele sunt numerotate în ordinea în care să se atașeze în continuare, dar această ordine de la stânga la dreapta merge 5, 3, 4, 1, 2.

fiecare element din ansamblul de mai sus poate fi furnizat de o companie separată care nu are cunoștințe despre produsul final sau celelalte piese ale acestuia. Atâta timp cât fiecare element modular este fabricat conform specificațiilor, veți putea crea dispozitivul final conform planificării.

putem reproduce asta în industria software?

sigur că putem! Prin utilizarea interfețelor și inversarea principiului controlului; cea mai bună parte este faptul că această abordare poate fi utilizată în orice limbaj orientat pe obiecte: Java, C#, Swift, TypeScript, JavaScript, PHP-lista continuă și continuă. Nu aveți nevoie de niciun cadru fantezist pentru a aplica această metodă. Trebuie doar să respectați câteva reguli simple și să rămâneți disciplinați.

inversarea controlului este prietenul tău

când am auzit prima dată despre inversarea controlului, mi-am dat seama imediat că am găsit o soluție. Este un concept de a lua dependențele existente și de a le inversa prin utilizarea interfețelor. Interfețele sunt simple declarații de metode. Nu oferă nicio implementare concretă. Ca urmare, ele pot fi folosite ca un acord între două elemente cu privire la modul de conectare a acestora. Ele pot fi folosite ca conectori modulari, dacă doriți. Atâta timp cât un element oferă interfața și un alt element oferă implementarea acesteia, ei pot lucra împreună fără să știe nimic unul despre celălalt. E genial.

să vedem pe un exemplu simplu cum putem decupla sistemul nostru pentru a crea cod modular. Diagramele de mai jos au fost implementate ca aplicații Java simple. Le puteți găsi pe acest depozit GitHub.

problemă

să presupunem că avem o aplicație foarte simplă constând doar dintr-o clasă Main, trei servicii și o singură clasă Util. Aceste elemente depind unele de altele în mai multe moduri. Mai jos, puteți vedea o implementare folosind abordarea “big ball of mud”. Clasele se cheamă pur și simplu. Ele sunt strâns cuplate și nu puteți scoate pur și simplu un element fără a atinge pe alții. Aplicațiile create folosind acest stil vă permit să creșteți inițial rapid. Cred că acest stil este potrivit pentru proiectele proof-of-concept, deoarece puteți juca cu ușurință lucrurile. Cu toate acestea, nu este potrivit pentru soluții gata de producție, deoarece chiar și întreținerea poate fi periculoasă și orice schimbare unică poate crea erori imprevizibile. Diagrama de mai jos arată această minge mare de arhitectură noroi.

principalele utilizează serviciile A, B și C, pe care fiecare le utilizează Util. Serviciul C folosește și serviciul A.

de ce injectarea dependenței a greșit totul

în căutarea unei abordări mai bune, putem folosi o tehnică numită injecție de dependență. Această metodă presupune că toate componentele ar trebui utilizate prin interfețe. Am citit susține că decuplează elemente, dar nu într-adevăr, deși? Nu. Aruncați o privire la diagrama de mai jos.

arhitectura anterioară, dar cu injecție de dependență. Acum main utilizează serviciul de interfață A, B și C, care sunt implementate de serviciile lor corespunzătoare. Serviciile A și C utilizează atât serviciul de interfață B, cât și interfața Util, care este implementată de Util. Serviciul C utilizează, de asemenea, serviciul de interfață A. fiecare serviciu împreună cu interfața sa este considerat a fi un element.

singura diferență între situația actuală și o minge mare de noroi este faptul că acum, în loc să apelăm direct la clase, le numim prin interfețele lor. Îmbunătățește ușor elementele de separare unele de altele. Dacă, de exemplu, doriți să reutilizați Service A într-un alt proiect, puteți face acest lucru scoțând Service A în sine, împreună cu Interface A, precum și Interface Bși Interface Util. După cum puteți vedea, Service A încă depinde de alte elemente. Drept urmare, avem în continuare probleme cu schimbarea codului într-un singur loc și încurcarea comportamentului în altul. Încă creează problema că, dacă modificați Service B și Interface B, va trebui să modificați toate elementele care depind de acesta. Această abordare nu rezolvă nimic; în opinia mea, adaugă doar un strat de interfață deasupra elementelor. Nu trebuie să injectați niciodată dependențe, ci ar trebui să scăpați de ele o dată pentru totdeauna. Ura pentru independență!

soluția pentru codul Modular

abordarea cred că rezolvă toate principalele dureri de cap ale dependențelor o face prin faptul că nu folosește deloc dependențele. Creați o componentă și ascultătorul acesteia. Un ascultător este o interfață simplă. Ori de câte ori trebuie să apelați o metodă din afara elementului curent, pur și simplu adăugați o metodă ascultătorului și o apelați în schimb. Elementul este permis doar să utilizeze fișiere, metode de apel în pachetul său și să utilizeze clase furnizate de main framework sau alte biblioteci utilizate. Mai jos, puteți vedea o diagramă a aplicației modificată pentru a utiliza arhitectura elementelor.

o diagramă a aplicației modificate pentru a utiliza arhitectura element. Principalele utilizări Util și toate cele trei servicii. Main implementează, de asemenea, un ascultător pentru fiecare serviciu, care este utilizat de acel serviciu. Un ascultător și un serviciu împreună sunt considerate a fi un element.

vă rugăm să rețineți că, în această arhitectură, numai Main clasa are mai multe dependențe. Conectează toate elementele împreună și încapsulează logica de afaceri a aplicației.

serviciile, pe de altă parte, sunt elemente complet independente. Acum, Puteți scoate fiecare serviciu din această aplicație și le puteți reutiliza în altă parte. Nu depind de nimic altceva. Dar stai, devine mai bine: nu trebuie să modificați aceste servicii niciodată, atâta timp cât nu le schimbați comportamentul. Atâta timp cât aceste servicii fac ceea ce ar trebui să facă, ele pot fi lăsate neatinse până la sfârșitul timpului. Acestea pot fi create de un inginer software profesionist sau de un coder pentru prima dată compromis de cel mai rău cod de spaghete pe care l-a gătit vreodată cineva cu declarații goto amestecate. Nu contează, pentru că logica lor este încapsulată. Oricât de oribil ar fi, nu se va vărsa niciodată la alte clase. Acest lucru vă oferă, de asemenea, puterea de a împărți munca într-un proiect între mai mulți dezvoltatori, unde fiecare dezvoltator poate lucra pe propria componentă independent, fără a fi nevoie să întrerupeți altul sau chiar să știți despre existența altor dezvoltatori.

în cele din urmă, puteți începe să scrieți cod independent încă o dată, la fel ca la începutul ultimului proiect.

model Element

să definim modelul element structural, astfel încât vom fi capabili să-l creeze într-un mod repetabil.

cea mai simplă versiune a elementului constă din două lucruri: O clasă de elemente principale și un ascultător. Dacă doriți să utilizați un element, atunci trebuie să implementați ascultătorul și să efectuați apeluri către clasa principală. Iată o diagramă a celei mai simple configurații:

o diagramă a unui singur element și ascultător sale într-o aplicație. Ca și până acum, aplicația folosește elementul, care folosește ascultătorul său, care este implementat de aplicație.

evident, va trebui să adăugați mai multă complexitate în element în cele din urmă, dar puteți face acest lucru cu ușurință. Asigurați-vă că niciuna dintre clasele dvs. logice nu depinde de alte fișiere din proiect. Ele pot utiliza numai cadrul principal, bibliotecile importate și alte fișiere din acest element. Când vine vorba de fișiere active precum imagini, vizualizări, sunete etc., de asemenea, ar trebui să fie încapsulate în elemente, astfel încât în viitor să fie ușor de reutilizat. Puteți copia pur și simplu întregul dosar într-un alt proiect și acolo este!

mai jos, puteți vedea un exemplu de grafic care prezintă un element mai avansat. Observați că constă dintr-o vedere pe care o utilizează și nu depinde de alte fișiere de aplicații. Dacă doriți să cunoașteți o metodă simplă de verificare a dependențelor, uitați-vă la secțiunea import. Există fișiere din afara elementului curent? Dacă da, atunci trebuie să eliminați aceste dependențe fie mutându-le în element, fie adăugând un apel adecvat ascultătorului.

o diagramă simplă a unui element mai complex. Aici, sensul mai larg al cuvântului "element" constă din șase părți: vedere; logica A, B și C; Element; și element ascultător. Relațiile dintre ultimele două și aplicație sunt aceleași ca înainte, dar elementul interior folosește și logica a și C. logica c folosește logica a și B. logica a folosește logica B și vizualizarea.

să aruncăm, de asemenea, o privire la un simplu exemplu “Hello World” creat în 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(); }}

inițial, definim ElementListener pentru a specifica metoda care imprimă ieșirea. Elementul în sine este definit mai jos. La apelarea sayHellope element, acesta imprimă pur și simplu un mesaj folosind ElementListener. Observați că elementul este complet independent de punerea în aplicare a printOutput metodă. Acesta poate fi imprimat în consola, o imprimantă fizică, sau un UI fantezie. Elementul nu depinde de această implementare. Datorită acestei abstractizări, acest element poate fi reutilizat cu ușurință în diferite aplicații.

acum aruncați o privire la clasa principală App. Implementează ascultătorul și asamblează elementul împreună cu implementarea concretă. Acum putem începe să o folosim.

de asemenea, puteți rula acest exemplu în JavaScript aici

arhitectura elementelor

să aruncăm o privire la utilizarea modelului elementului într-o aplicație la scară largă. Este un lucru să—l arăți într-un proiect mic-este altul să-l aplici în lumea reală.

structura unei aplicații web full-stack pe care îmi place să o folosesc arată după cum urmează:

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

într-un folder de cod sursă, am împărțit inițial fișierele client și server. Este un lucru rezonabil de făcut, deoarece acestea rulează în două medii diferite: browserul și serverul back-end.

apoi împărțim codul din fiecare strat în foldere numite app and elements. Elemente este format din foldere cu componente independente, în timp ce folderul app Fire toate elementele împreună și stochează toate logica de afaceri.

în acest fel, elementele pot fi reutilizate între diferite proiecte, în timp ce toată complexitatea specifică aplicației este încapsulată într-un singur folder și destul de des redusă la apeluri simple către elemente.

Hands-on exemplu

crezând că practica întotdeauna trump teorie, să aruncăm o privire la un exemplu din viața reală creat în nod.js și dactilografiere.

exemplu din viața reală

este o aplicație web foarte simplă care poate fi utilizată ca punct de plecare pentru soluții mai avansate. Urmează arhitectura elementului, precum și folosește un model de element structural extensiv.

din evidențieri, puteți vedea că pagina principală a fost distinsă ca element. Această pagină include propria sa vizualizare. Deci, atunci când, de exemplu, doriți să-l reutilizați, puteți copia pur și simplu întregul dosar și fixați-l într-un alt proiect. Doar sârmă totul împreună și vă sunt stabilite.

este un exemplu de bază care demonstrează că puteți începe să introduceți elemente în propria aplicație astăzi. Puteți începe să distingeți componentele independente și să le separați logica. Nu contează cât de dezordonat este codul la care lucrați în prezent.

Dezvoltați Mai Repede, Reutilizați Mai Des!

sper că, cu acest nou set de instrumente, veți putea dezvolta mai ușor codul care este mai ușor de întreținut. Înainte de a sări în utilizarea modelului elementului în practică, să recapitulăm rapid toate punctele principale:

  • o mulțime de probleme în software-ul se întâmplă din cauza dependențelor între mai multe componente.

  • făcând o schimbare într-un singur loc, puteți introduce un comportament imprevizibil în altă parte.

trei abordări arhitecturale comune sunt:

  • mingea mare de noroi. Este minunat pentru dezvoltarea rapidă, dar nu atât de mare pentru scopuri de producție stabile.

  • injecție de dependență. Este o soluție pe jumătate coaptă pe care ar trebui să o evitați.

  • Element de arhitectură. Această soluție vă permite să creați componente independente și să le reutilizați în alte proiecte. Este întreținut și genial pentru lansări de producție stabile.

modelul elementului de bază constă dintr-o clasă principală care are toate metodele majore, precum și un ascultător care este o interfață simplă care permite comunicarea cu lumea externă.

pentru a obține arhitectura elementului full-stack, mai întâi separați front-end-ul de codul back-end. Apoi creați un folder în fiecare pentru o aplicație și elemente. Folderul Elemente este format din toate elementele independente, în timp ce folderul aplicației leagă totul împreună.

acum Puteți începe să creați și să partajați propriile elemente. Pe termen lung, vă va ajuta să creați produse ușor de întreținut. Mult noroc și lasă-mă să știu ce ai creat!

de asemenea, dacă vă aflați în optimizarea prematură a codului, citiți Cum să evitați Blestemul optimizării Premature de către colegul Toptaler Kevin Bloch.

Lasă un răspuns

Adresa ta de email nu va fi publicată.