Monkey Patching i dynamiska programmeringsspråk: ett JavaScript-exempel

Javascript
dynamisk programmering
Monkey Patching i dynamiska programmeringsspråk cover image

Introduktion

Den här artikeln kommer att utforska begreppen dynamiska och statiska programmeringsspråk, de huvudsakliga skillnaderna mellan de två och vad varje paradigm ger när det gäller fördelar och fallgropar. Denna utforskning kommer att fokusera ytterligare på dynamiska programmeringsspråk, särskilt ett av de väsentliga mönstren det möjliggör: Monkey Patch, detta mönster kommer att visas upp med hjälp av ett exempel i JavaScript.

Dynamiska vs statiska programmeringsspråk

Terminologi

För att förstå vad som utgör ett dynamiskt språk eller ett statiskt språk måste vi skapa en förståelse för några nyckeltermer som ofta används i detta sammanhang: Kompileringstid, Runtime och *Typkontroll *.

Kompilera och körtid är två termer som motsvarar olika stadier i ett datorprograms livscykel, med början i kompileringstid.

Kompileringstid

Kompileringstid är det första steget i ett programs livscykel. En utvecklare skriver kod i ett givet programmeringsspråk. Oftare än inte kan maskinen inte förstå koden som är skriven på ett högnivåspråk, så en dedikerad kompilator används för att översätta den till ett mellanformat på lägre nivå som blir klart för exekvering.

Körtid

Runtime kapslar vanligtvis två steg: ladda programmet i minnet genom att allokera de resurser som behövs för dess exekvering tillsammans med dess instruktioner, och sedan exekvera programmet enligt instruktionernas ordning.

Följande diagram illustrerar denna process:

Typkontroll

Typkontroll är en inbyggd funktion i nästan alla programmeringsspråk. Det är möjligheten att kontrollera om ett värde som tilldelats en given variabel motsvarar den korrekta typen av den variabeln. Varje programmeringsspråk har olika sätt att representera ett värde av en given typ i minnet. Dessa olika representationer gör det möjligt att kontrollera överensstämmelsen mellan typen av ett värde och typen av en variabel du försöker tilldela det värdet till.

Nu när vi har en hög nivå av förståelse för ett programs livscykel och typkontroll, kan vi fortsätta att utforska statiska programmeringsspråk.

Statiska programmeringsspråk

Statiska programmeringsspråk, även kallade statiskt typade språk, är språk som tillämpar den typkontroll som vi nämnde i kompileringsfasen. Detta innebär i praktiken att en variabel behåller sin typ från deklaration och inget värde kan tilldelas den annat än värden från dess deklarationstyp. Statiska programmeringsspråk erbjuder extra säkerhet när de hanterar typer men kan bromsa utvecklingsprocessen i vissa användningsfall när detta blir en hård begränsning.

Dynamiska programmeringsspråk

Dynamiska programmeringsspråk, å andra sidan, tillämpar typkontroll vid körning. Detta innebär att vilken variabel som helst kan hålla vilket värde som helst när som helst i programmet. Detta kan vara fördelaktigt eftersom det erbjuder en nivå av flexibilitet för utvecklaren som inte finns i de statiska språken. Dynamiska språk tenderar att vara långsammare vid exekvering än sina statiska motsvarigheter eftersom de involverar ytterligare ett steg för att dynamiskt räkna ut skrivningen av varje variabel.

Monkey Patch

Statisk vs dynamisk typning är en grundläggande egenskap i ett programmeringsspråk, att gå med ett paradigm framför det andra kan möjliggöra en mängd olika mönster och metoder som avsevärt kan förbättra kvaliteten och utvecklingshastigheten. Det kan också öppna dörren för många begränsningar och antimönster om inga noggranna överväganden görs vid designbeslut.

Speciellt är dynamiskt typade programmeringsspråk kända för att erbjuda en högre nivå av flexibilitet eftersom de inte begränsar en variabel till en enda typ. Denna flexibilitet kommer med kostnaden för ytterligare ansvar för utvecklaren vid implementering och felsökning av program för att säkerställa att inga oförutsägbara beteenden inträffar. Aplappsmönstret kommer från denna filosofi.

Monkey Patch hänvisar till processen att utöka/ändra hur en komponent fungerar under körning. Komponenten i fråga kan vara ett bibliotek, en klass, en metod eller till och med en modul. Tanken är densamma: en kodbit är gjord för att utföra en viss uppgift, och målet med monkey patching är att ändra eller utöka beteendet hos den kodbiten så att den utför en ny uppgift, allt utan att ändra själva koden .

Detta är möjligt i dynamiskt programmeringsspråk eftersom oavsett vilken typ av komponent vi har att göra med, har den fortfarande samma struktur som ett objekt med olika attribut, attributen kan innehålla metoder som kan omfördelas för att uppnå ett nytt beteende i objektet utan att gå in på dess interna detaljer och detaljer om genomförandet. Detta blir särskilt användbart i fallet med tredje parts bibliotek och moduler eftersom de tenderar att vara svårare att justera.

Följande exempel kommer att visa upp ett vanligt användningsfall som kan dra nytta av att använda monkey patch-tekniken. Javascript användes för implementeringens skull här men detta borde fortfarande gälla i stort sett alla andra dynamiska programmeringsspråk.

Exempel

Implementera ett minimalt testramverk med Nodes Native HTTP-modul

Enhets- och integrationstestning kan falla under användningsfallen Monkey-patching. De involverar vanligtvis testfall som sträcker sig över mer än en tjänst för integrationstestning, eller API- och/eller databasberoenden för enhetstestning. I dessa två scenarier, och för att uppnå målen med testning i första hand, skulle vi vilja att våra tester var oberoende av dessa externa resurser. Sättet att uppnå detta är genom hån. Mocking simulerar beteendet hos externa tjänster så att testet kan fokusera på kodens faktiska logik. Monkey patching kan vara till hjälp här eftersom det kan modifiera metoderna för de externa tjänsterna genom att ersätta dem med platshållarmetoder som vi kallar "stub". Dessa metoder returnerar det förväntade resultatet i testfallen så att vi kan undvika att initiera förfrågningar till produktionstjänster bara för testernas skull.

Följande exempel är en enkel implementering av Monkey-patchning på NodeJs inbyggda http-modul. http-modulen är gränssnittet som implementerar http-protokollmetoderna för NodeJs. Det används främst för att skapa barebone http-servrar och kommunicera med externa tjänster med hjälp av http-protokollet.

I exemplet nedan har vi ett enkelt testfall där vi anropar en extern tjänst för att hämta listan med användar-ID. Istället för att anropa den faktiska tjänsten patchar vi http get-metoden så att den bara returnerar det förväntade resultatet som är en rad slumpmässiga användar-ID. Detta kanske inte verkar vara av stor betydelse eftersom vi bara hämtar data, men om vi implementerar ett annat testfall som involverar ändring av data av något slag, kan vi av misstag ändra data om produktionen när tester körs.

På så sätt kan vi implementera våra funktioner och skriva tester för varje funktionalitet samtidigt som vi garanterar säkerheten för våra produktionstjänster.

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

Koden ovan är enkel, vi importerar http-modulen, omtilldelar http.get-metoden med en ny metod som bara returnerar en array av id. Nu kallar vi den nya patchade metoden inuti testfallet och vi får det nya förväntade resultatet.

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

Vanliga fallgropar och begränsningar

Det borde inte komma som någon överraskning att appatchning har sina egna brister och begränsningar. I samband med moduler i nodmodulsystemet anses patcha en global modul som http vara en operation med biverkningar, detta beror på att http är tillgänglig från vilken punkt som helst inuti kodbasen och vilken annan enhet som helst kan ha ett beroende av den. Dessa enheter förväntar sig att http-modulen fungerar i sitt vanliga beteende, genom att ändra en av http-metoderna bryter vi effektivt alla andra http-beroenden inuti kodbasen.

Eftersom vi arbetar inom ett dynamiskt skrivet språk, kanske saker och ting inte misslyckas omedelbart och hellre förfaller till ett oförutsägbart beteende som gör felsökning till en extremt komplex uppgift. I andra användningsfall kan det finnas två olika patchar av samma komponent på samma attribut, i vilket fall vi inte riktigt kan förutsäga vilken patch som kommer att ha företräde framför den andra vilket resulterar i en ännu mer oförutsägbar kod.

Det är också viktigt att nämna att monkey patching kan ha små variationer i beteende mellan olika programmeringsspråk. Allt beror på språkdesign och val av implementering. Till exempel, i python kommer inte alla instanser som använder en patchad metod att påverkas av patchen. Om en instans uttryckligen anropar den korrigerade metoden kommer den att få den nya uppdaterade versionen, tvärtom, andra instanser som kanske bara har attribut som pekar på den korrigerade metoden och inte uttryckligen anropar den kommer att få den ursprungliga versionen, detta beror på hur python bindande i klasser fungerar.

Slutsats

I den här artikeln utforskade vi distinktionerna på hög nivå mellan statiska och dynamiska programmeringsspråk, vi såg hur dynamiska programmeringsspråk kan dra nytta av nya paradigm och mönster som utnyttjar den inneboende flexibiliteten som dessa språk erbjuder. Exemplet vi visade upp var relaterat till Monkey-patching, en teknik som används för att utöka kodens beteende utan att ändra den från källan. Vi såg ett fall där användningen av denna teknik skulle vara fördelaktig tillsammans med dess potentiella nackdelar. Mjukvaruutveckling handlar om avvägningar, och att använda rätt lösning för problemet kräver utarbetade överväganden från utvecklaren och en god förståelse för arkitekturens principer och grunder.


Career Services background pattern

Karriärtjänster

Contact Section background image

Låt oss hålla kontakten

Code Labs Academy © 2024 Alla rättigheter förbehållna.