Monkey Patching in dynamische programmeertalen: een JavaScript-voorbeeld

Javascript
dynamisch programmeren
Monkey Patching in dynamische programmeertalen cover image

Introductie

Dit artikel onderzoekt de concepten van dynamische en statische programmeertalen, de belangrijkste verschillen tussen de twee, en wat elk paradigma biedt in termen van voordelen en valkuilen. Deze verkenning zal zich verder richten op dynamische programmeertalen, met name op een van de essentiële patronen die deze mogelijk maakt: Monkey Patch, dit patroon zal worden gedemonstreerd met behulp van een voorbeeld in JavaScript.

Dynamische versus statische programmeertalen

Terminologie

Om te begrijpen wat een dynamische taal of een statische taal is, moeten we inzicht krijgen in een aantal belangrijke termen die vaak in deze context worden gebruikt: Compileertijd, Runtime en *Typecontrole *.

Compile en Runtime zijn twee termen die overeenkomen met verschillende fasen in de levenscyclus van een computerprogramma, te beginnen met Compile-tijd.

Compileertijd

Compileertijd is de eerste stap in de levenscyclus van een programma. Een ontwikkelaar schrijft code in een bepaalde programmeertaal. Vaker wel dan niet is de machine niet in staat de code te begrijpen die in een taal op hoog niveau is geschreven, dus wordt een speciale compiler gebruikt om deze te vertalen naar een tussenformaat op een lager niveau dat klaar is voor uitvoering.

Runtime

Runtime omvat meestal twee stappen: het laden van het programma in het geheugen door de middelen toe te wijzen die nodig zijn voor de uitvoering ervan, samen met de instructies, en vervolgens het programma uit te voeren in de volgorde van die instructies.

Het volgende diagram illustreert dit proces:

Typecontrole

Typecontrole is een ingebouwde functie in bijna alle programmeertalen. Het is de mogelijkheid om te controleren of een waarde die aan een bepaalde variabele is toegewezen, overeenkomt met het juiste type van die variabele. Elke programmeertaal heeft een andere manier om een ​​waarde van een bepaald type in het geheugen weer te geven. Deze verschillende representaties maken het mogelijk om de overeenkomst te controleren tussen het type waarde en het type variabele waaraan u die waarde probeert toe te wijzen.

Nu we een goed begrip hebben van de levenscyclus van een programma en typecontrole, kunnen we doorgaan met het verkennen van statische programmeertalen.

Statische programmeertalen

Statische programmeertalen, ook wel statisch getypeerde talen genoemd, zijn talen die de typecontrole toepassen die we tijdens de compileerfase noemden. Dit betekent in feite dat een variabele zijn type van declaratie behoudt en dat er geen andere waarde aan kan worden toegewezen dan de waarden van zijn declaratietype. Statische programmeertalen bieden extra veiligheid bij het omgaan met typen, maar kunnen het ontwikkelingsproces in bepaalde gebruikssituaties vertragen als dit een harde beperking wordt.

Dynamische programmeertalen

Dynamische programmeertalen passen daarentegen typecontrole toe tijdens runtime. Dit betekent dat elke variabele op elk punt in het programma elke waarde kan bevatten. Dit kan nuttig zijn omdat het de ontwikkelaar een mate van flexibiliteit biedt die niet aanwezig is in de statische talen. Dynamische talen zijn doorgaans langzamer in uitvoering dan hun statische tegenhangers, omdat ze een extra stap met zich meebrengen bij het dynamisch uitzoeken van het typen van elke variabele.

Apenpatch

Statisch versus dynamisch typen is een fundamentele eigenschap van een programmeertaal. Door het ene paradigma boven het andere te gebruiken, kunnen een groot aantal verschillende patronen en praktijken mogelijk worden gemaakt die de kwaliteit en de snelheid van de ontwikkeling aanzienlijk kunnen verbeteren. Het kan ook de deur openen voor veel beperkingen en antipatronen als er geen zorgvuldige afwegingen worden gemaakt bij het nemen van ontwerpbeslissingen.

Het is vooral bekend dat dynamisch getypeerde programmeertalen een hoger niveau van flexibiliteit bieden, omdat ze een variabele niet beperken tot een enkel type. Deze flexibiliteit brengt de kosten met zich mee van extra verantwoordelijkheid voor de ontwikkelaar bij het implementeren en debuggen van programma's om er zeker van te zijn dat er geen onvoorspelbaar gedrag optreedt. Het apenpatchpatroon komt voort uit deze filosofie.

Monkey Patch verwijst naar het proces van het uitbreiden/wijzigen van de werking van een component tijdens runtime. De component in kwestie kan een bibliotheek, een klasse, een methode of zelfs een module zijn. Het idee is hetzelfde: een stukje code wordt gemaakt om een ​​bepaalde taak te volbrengen, en het doel van Monkey Patching is om het gedrag van dat stukje code te veranderen of uit te breiden, zodat het een nieuwe taak vervult, en dat allemaal zonder de code zelf te veranderen. .

Dit wordt mogelijk gemaakt in een dynamische programmeertaal, omdat het, ongeacht met welk type component we te maken hebben, nog steeds dezelfde structuur van een object heeft met verschillende attributen. De attributen kunnen methoden bevatten die opnieuw kunnen worden toegewezen om nieuw gedrag in het object te bereiken zonder in te gaan op de interne details en details van de implementatie. Dit wordt vooral handig in het geval van bibliotheken en modules van derden, omdat deze doorgaans moeilijker te tweaken zijn.

Het volgende voorbeeld toont een veelvoorkomend gebruiksscenario dat baat kan hebben bij het gebruik van de Monkey Patch-techniek. Voor de implementatie werd hier Javascript gebruikt, maar dit zou nog steeds in grote lijnen van toepassing moeten zijn op elke andere dynamische programmeertaal.

Voorbeeld

Implementeer een minimaal testframework met de native HTTP-module van Node

Unit- en integratietests kunnen onder de use cases van Monkey-patching vallen. Meestal gaat het om testcases die zich over meer dan één service uitstrekken voor integratietesten, of om API- en/of database-afhankelijkheden voor het testen van eenheden. In deze twee scenario's, en om de doelstellingen van het testen in de eerste plaats te bereiken, zouden we willen dat onze tests onafhankelijk zijn van deze externe bronnen. De manier om dit te bereiken is door te spotten. Mocking simuleert het gedrag van externe services, zodat de test zich kan concentreren op de feitelijke logica van de code. Monkey-patching kan hier nuttig zijn, omdat het de methoden van de externe services kan wijzigen door ze te vervangen door tijdelijke aanduiding-methoden die we 'stub' noemen. Deze methoden retourneren het verwachte resultaat in de testgevallen, zodat we kunnen voorkomen dat we verzoeken aan productieservices initiëren alleen omwille van tests.

Het volgende voorbeeld is een eenvoudige implementatie van Monkey-patching op de native http-module van NodeJs. De http-module is de interface die de http-protocolmethoden voor NodeJs implementeert. Het wordt voornamelijk gebruikt om barebone http-servers te maken en te communiceren met externe services via het http-protocol.

In het onderstaande voorbeeld hebben we een eenvoudige testcase waarbij we een externe service aanroepen om de lijst met gebruikers-ID's op te halen. In plaats van de daadwerkelijke service aan te roepen, patchen we de http get-methode, zodat deze alleen het verwachte resultaat retourneert, namelijk een reeks willekeurige gebruikers-ID's. Dit lijkt misschien niet zo belangrijk omdat we alleen maar gegevens ophalen, maar als we nog een testcase implementeren waarbij gegevens op de een of andere manier moeten worden gewijzigd, kunnen we per ongeluk gegevens over de productie wijzigen tijdens het uitvoeren van tests.

Op deze manier kunnen we onze functionaliteiten implementeren en voor elke functionaliteit testen schrijven, terwijl we de veiligheid van onze productiediensten garanderen.

// 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");
});

De bovenstaande code is eenvoudig: we importeren de http-module en wijzen de http.get-methode opnieuw toe met een nieuwe methode die alleen een reeks ID's retourneert. Nu roepen we de nieuwe patched-methode aan in de testcase en krijgen we het nieuwe verwachte resultaat.

~/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.

Veelvoorkomende valkuilen en beperkingen

Het mag geen verrassing zijn dat het patchen van apen zijn eigen tekortkomingen en beperkingen heeft. In de context van modules in het knooppuntmodulesysteem wordt het patchen van een globale module zoals http beschouwd als een bewerking met bijwerkingen. Dit komt omdat http toegankelijk is vanaf elk punt binnen de codebase en elke andere entiteit er afhankelijk van kan zijn. Deze entiteiten verwachten dat de http-module in zijn gebruikelijke gedrag werkt. Door een van de http-methoden te wijzigen, doorbreken we effectief alle andere http-afhankelijkheden binnen de codebase.

Omdat we in een dynamisch getypeerde taal opereren, zullen de zaken niet meteen mislukken en zullen ze liever een onvoorspelbaar gedrag vertonen, wat het debuggen tot een uiterst complexe taak maakt. In andere gebruiksgevallen kunnen er twee verschillende patches van dezelfde component op hetzelfde attribuut zijn, in welk geval we niet echt kunnen voorspellen welke patch voorrang zal hebben op de andere, wat resulteert in een nog onvoorspelbaardere code.

Het is ook belangrijk om te vermelden dat Monkey Patching kleine verschillen in gedrag tussen verschillende programmeertalen kan hebben. Het hangt allemaal af van het taalontwerp en de implementatiekeuzes. In Python worden bijvoorbeeld niet alle instanties die een gepatchte methode gebruiken, door de patch beïnvloed. Als een instantie expliciet de gepatchte methode aanroept, krijgt deze de nieuwe bijgewerkte versie. Integendeel, andere instanties die mogelijk alleen attributen hebben die naar de gepatchte methode verwijzen en deze niet expliciet aanroepen, krijgen de originele versie. Dit komt door de manier waarop Python klassenbinding werkt.

Conclusie

In dit artikel hebben we de verschillen op hoog niveau tussen statische en dynamische programmeertalen onderzocht. We hebben gezien hoe dynamische programmeertalen kunnen profiteren van nieuwe paradigma's en patronen die gebruik maken van de inherente flexibiliteit die deze talen bieden. Het voorbeeld dat we lieten zien had betrekking op Monkey patching, een techniek die wordt gebruikt om het gedrag van code uit te breiden zonder deze van de bron te veranderen. We hebben een geval gezien waarin het gebruik van deze techniek nuttig zou zijn, samen met de mogelijke nadelen ervan. Bij softwareontwikkeling draait alles om afwegingen, en het gebruik van de juiste oplossing voor het probleem vereist uitgebreide overwegingen van de ontwikkelaar en een goed begrip van de architectuurprincipes en -fundamenten.


Career Services background pattern

Carrièrediensten

Contact Section background image

Laten we in contact blijven

Code Labs Academy © 2024 Alle rechten voorbehouden.