Monkey Patching în limbaje de programare dinamică: un exemplu JavaScript

Javascript
programare dinamică
Monkey Patching în limbaje de programare dinamică cover image

Introducere

Acest articol va explora conceptele limbajelor de programare dinamice și statice, principalele diferențe dintre cele două și ceea ce oferă fiecare paradigmă în ceea ce privește avantajele și capcanele. Această explorare se va concentra în continuare asupra limbajelor de programare dinamice, în special pe unul dintre modelele esențiale pe care le permite: Monkey Patch, acest model va fi prezentat cu ajutorul unui exemplu în JavaScript.

Limbaje de programare dinamice vs statice

Terminologie

Pentru a înțelege ce constituie un limbaj dinamic sau unul static, trebuie să stabilim o înțelegere a câțiva termeni cheie folosiți în mod obișnuit în acest context: Timp de compilare, Runtime și *Verificare tip *.

Compile și Runtime sunt doi termeni care corespund unor etape diferite din ciclul de viață al unui program de calculator, începând cu timpul de compilare.

Timp de compilare

Timpul de compilare este primul pas în ciclul de viață al unui program. Un dezvoltator scrie cod într-un anumit limbaj de programare. De cele mai multe ori, mașina nu poate înțelege codul scris într-un limbaj de nivel înalt, așa că este folosit un compilator dedicat pentru a-l traduce într-un format intermediar de nivel inferior care devine gata de execuție.

Runtime

Runtime încapsulează de obicei doi pași: încărcarea programului în memorie prin alocarea resurselor necesare execuției sale împreună cu instrucțiunile sale și apoi executarea programului urmând ordinea acelor instrucțiuni.

Următoarea diagramă ilustrează acest proces:

Verificare tip

Verificarea tipului este o caracteristică încorporată în aproape toate limbajele de programare. Este capacitatea de a verifica dacă o valoare atribuită unei anumite variabile corespunde tipului corect al acelei variabile. Fiecare limbaj de programare are un mod diferit de a reprezenta o valoare de un anumit tip în memorie. Aceste reprezentări diferite fac posibilă verificarea corespondenței dintre tipul unei valori și tipul unei variabile căreia încercați să îi atribuiți acea valoare.

Acum că avem o înțelegere la nivel înalt a ciclului de viață a unui program și a verificării tipului, putem continua să exploram limbaje de programare statice.

Limbaje de programare statică

Limbaje de programare statică, denumite și limbaje tipizate static, sunt limbaje care aplică verificarea tipului pe care am menționat-o în faza de compilare. Acest lucru înseamnă efectiv că o variabilă își păstrează tipul din declarație și nu i se poate atribui nicio valoare în afară de valorile din tipul ei de declarație. Limbajele de programare statice oferă o siguranță suplimentară atunci când aveți de-a face cu tipuri, dar pot încetini procesul de dezvoltare în anumite cazuri de utilizare când aceasta devine o restricție dură.

Limbaje de programare dinamică

Limbajele de programare dinamică, pe de altă parte, aplică verificarea tipului în timpul execuției. Aceasta înseamnă că orice variabilă poate deține orice valoare în orice punct al programului. Acest lucru poate fi benefic deoarece oferă dezvoltatorului un nivel de flexibilitate care nu este prezent în limbajele statice. Limbile dinamice tind să fie mai lente la execuție decât omologii lor statici, deoarece implică o etapă suplimentară de stabilire dinamică a tastării fiecărei variabile.

Petec de maimuță

Tastarea statică vs. dinamică este o trăsătură fundamentală într-un limbaj de programare, mergând cu o paradigmă față de cealaltă poate permite o serie de modele și practici diferite care pot îmbunătăți semnificativ calitatea și viteza de dezvoltare. De asemenea, poate deschide ușa pentru multe limitări și anti-modele dacă nu sunt luate considerații atente atunci când luați decizii de proiectare.

În special, limbajele de programare cu tastare dinamică sunt cunoscute că oferă un nivel mai ridicat de flexibilitate, deoarece nu limitează o variabilă la un singur tip. Această flexibilitate vine cu costul unei responsabilități suplimentare pentru dezvoltator atunci când implementează și depanează programe pentru a se asigura că nu apar comportamente imprevizibile. Modelul de plasture de maimuță provine din această filozofie.

Monkey Patch se referă la procesul de extindere/modificare a funcționării unei componente în timpul execuției. Componenta în cauză poate fi o bibliotecă, o clasă, o metodă sau chiar un modul. Ideea este aceeași: o bucată de cod este făcută pentru a îndeplini o anumită sarcină, iar scopul corecțiilor maimuțelor este de a schimba sau extinde comportamentul acelei bucăți de cod, astfel încât să îndeplinească o nouă sarcină, totul fără a schimba codul în sine. .

Acest lucru este posibil în limbajul de programare dinamic, deoarece indiferent de tipul de componentă cu care avem de-a face, aceasta are totuși aceeași structură a unui obiect cu atribute diferite, atributele pot conține metode care pot fi realocate pentru a obține un nou comportament în obiect. fără a intra în interiorul său și detaliile de implementare. Acest lucru devine deosebit de util în cazul bibliotecilor și modulelor terțe, deoarece acestea tind să fie mai greu de modificat.

Următorul exemplu va prezenta un caz obișnuit de utilizare care poate beneficia de utilizarea tehnicii plasturii maimuțelor. Javascript a fost folosit de dragul implementării aici, dar acest lucru ar trebui să se aplice în general oricărui alt limbaj de programare dinamică.

Exemplu

Implementați un cadru minim de testare cu modulul HTTP nativ al Node

Testarea unitară și de integrare pot fi incluse în cazurile de utilizare ale corecțiilor Monkey. Acestea implică, de obicei, cazuri de testare care se întind pe mai mult de un serviciu pentru testarea integrării sau dependențe de API și/sau baze de date pentru testarea unitară. În aceste două scenarii, și pentru a îndeplini obiectivele testării, în primul rând, am dori ca testele noastre să fie independente de aceste resurse externe. Modul de a realiza acest lucru este prin batjocura. Mocking-ul este simularea comportamentului serviciilor externe, astfel încât testul se poate concentra pe logica reală a codului. Patch-ul maimuță poate fi util aici, deoarece poate modifica metodele serviciilor externe prin înlocuirea lor cu metode substituenți pe care le numim „stub”. Aceste metode returnează rezultatul așteptat în cazurile de testare, astfel încât să putem evita inițierea solicitărilor către serviciile de producție doar de dragul testelor.

Următorul exemplu este o implementare simplă a corecțiilor Monkey pe modulul http nativ NodeJ. Modulul http este interfața care implementează metodele de protocol http pentru NodeJ. Este folosit în principal pentru a crea servere http barebone și pentru a comunica cu servicii externe folosind protocolul http.

În exemplul de mai jos avem un caz simplu de testare în care apelăm un serviciu extern pentru a prelua lista de ID-uri de utilizator. În loc să apelăm serviciul propriu-zis, corectăm metoda http get, astfel încât să returneze doar rezultatul așteptat, care este o serie de ID-uri aleatoare ale utilizatorului. Acest lucru s-ar putea să nu pară de mare importanță, deoarece doar preluăm date, dar dacă implementăm un alt caz de testare care implică modificarea unor date, s-ar putea să modificăm accidental datele de producție la rularea testelor.

În acest fel, putem implementa funcționalitățile noastre și putem scrie teste pentru fiecare funcționalitate, asigurând în același timp siguranța serviciilor noastre de producție.

// import the http module
let http = require("http");

// patch the get method of the http module
http.get = async function(url) {
  return {
    data: ["1234", "1235", "1236", "1236"]
  };
}

// example test suite, call new patched get method for testing
test('get array of user ids from users api', async () => {
  const res = await http.get("https://users.api.com/ids");
  const userIds = res.data;
  expect(userIds).toBeDefined();
  expect(userIds.length).toBe(4);
  expect(userIds[0]).toBe("1234");
});

Codul de mai sus este simplu, importăm modulul http, reatribuim metoda http.get cu o nouă metodă care returnează doar o serie de ID-uri. Acum apelăm noua metodă corecționată în interiorul cazului de testare și obținem noul rezultat așteptat.

~/SphericalTartWorker$ npm test

> nodejs@1.0.0 test
> jest

PASS  ./index.test.js
  ✓ get array of user ids from users api (25 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        0.977 s, estimated 2 s
Ran all test suites.

Capcane și limitări comune

Nu ar trebui să fie surprinzător că patch-ul maimuțelor are propriile defecte și limitări. În contextul modulelor din sistemul de module nod, corecția unui modul global precum http este considerată o operație cu efecte secundare, deoarece http este accesibil din orice punct din baza de cod și orice altă entitate ar putea avea o dependență de el. Aceste entități se așteaptă ca modulul http să funcționeze în comportamentul său obișnuit, prin schimbarea uneia dintre metodele http, distrugem efectiv toate celelalte dependențe http din baza de cod.

Deoarece operam într-un limbaj tip dinamic, lucrurile s-ar putea să nu eșueze imediat și mai degrabă ar fi implicit la un comportament imprevizibil care face depanarea o sarcină extrem de complexă. În alte cazuri de utilizare, ar putea exista două patch-uri diferite ale aceleiași componente pe același atribut, caz în care nu putem prezice cu adevărat care patch va avea prioritate față de celălalt, rezultând un cod și mai imprevizibil.

De asemenea, este important să menționăm că corecțiile maimuțelor ar putea avea ușoare variații de comportament între diferitele limbaje de programare. Totul depinde de designul limbii și de alegerile de implementare. De exemplu, în python, nu toate instanțele care utilizează o metodă corecționată vor fi afectate de patch. Dacă o instanță apelează în mod explicit metoda corectată, atunci va primi noua versiune actualizată, dimpotrivă, alte instanțe care ar putea avea doar atribute care indică metoda corectată și nu o apelează în mod explicit vor obține versiunea originală, acest lucru se datorează modului în care python functioneaza obligatoriu in clase.

Concluzie

În acest articol, am explorat diferențele de nivel înalt dintre limbajele de programare statice și dinamice, am văzut cum limbajele de programare dinamică pot beneficia de noile paradigme și modele care profită de flexibilitatea inerentă oferită de aceste limbaje. Exemplul pe care l-am prezentat a fost legat de monkey patching, o tehnică folosită pentru a extinde comportamentul codului fără a-l schimba de la sursă. Am văzut un caz în care utilizarea acestei tehnici ar fi benefică împreună cu potențialele sale dezavantaje. Dezvoltarea de software se referă la compromisuri, iar utilizarea soluției potrivite pentru problemă necesită considerații elaborate din partea dezvoltatorului și o bună înțelegere a principiilor și fundamentelor arhitecturii.


Career Services background pattern

Servicii de carieră

Contact Section background image

Să rămânem în legătură

Code Labs Academy © 2024 Toate drepturile rezervate.