Monkey Patching en linguaxes de programación dinámica: un exemplo de JavaScript

Javascript
programación dinámica
Monkey Patching en linguaxes de programación dinámica cover image

Introdución

Este artigo explorará os conceptos das linguaxes de programación dinámica e estática, as principais diferenzas entre ambas e o que proporciona cada paradigma en canto a vantaxes e trampas. Esta exploración centrarase aínda máis nas linguaxes de programación dinámicas, en particular nun dos patróns esenciais que permite: Monkey Patch, este patrón mostrarase coa axuda dun exemplo en JavaScript.

Linguaxes de programación dinámicas vs estáticas

Terminoloxía

Para comprender o que constitúe unha linguaxe dinámica ou estática, necesitamos comprender algúns termos clave que se usan habitualmente neste contexto: Tempo de compilación, Tiempo de execución e *Comprobación de tipo *.

Compilar e Runtime son dous termos que corresponden a diferentes etapas do ciclo de vida dun programa informático, comezando polo tempo de compilación.

Tempo de compilación

O tempo de compilación é o primeiro paso no ciclo de vida dun programa. Un programador escribe código nunha linguaxe de programación determinada. Na maioría das veces, a máquina non pode comprender o código escrito nunha linguaxe de alto nivel, polo que se utiliza un compilador dedicado para traducilo a un formato intermedio de nivel inferior que está listo para a súa execución.

Tempo de execución

O tempo de execución xeralmente encapsula dous pasos: cargar o programa na memoria asignando os recursos necesarios para a súa execución xunto coas súas instrucións, e despois executar o programa seguindo a orde desas instrucións.

O seguinte diagrama ilustra este proceso:

Comprobación de tipos

A verificación de tipo é unha función integrada en case todas as linguaxes de programación. É a capacidade de comprobar se un valor asignado a unha determinada variable corresponde ao tipo correcto desa variable. Cada linguaxe de programación ten unha forma diferente de representar un valor dun tipo determinado na memoria. Estas diferentes representacións permiten comprobar a correspondencia entre o tipo de valor e o tipo de variable á que se intenta asignar ese valor.

Agora que temos un alto nivel de comprensión do ciclo de vida dun programa e da comprobación de tipo, podemos continuar a explorar linguaxes de programación estáticas.

Linguaxes de programación estática

As linguaxes de programación estática, tamén coñecidas como linguaxes de tipo estático, son linguaxes que aplican a comprobación de tipo que mencionamos na fase de compilación. Isto significa efectivamente que unha variable mantén o seu tipo de declaración e non se lle pode asignar ningún valor que non sexa os valores do seu tipo de declaración. As linguaxes de programación estáticas ofrecen unha seguridade adicional ao tratar con tipos, pero poden retardar o proceso de desenvolvemento en certos casos de uso cando isto se converte nunha restrición dura.

Linguaxes de programación dinámica

As linguaxes de programación dinámica, por outra banda, aplican a comprobación de tipo en tempo de execución. Isto significa que calquera variable pode conter calquera valor en calquera punto do programa. Isto pode ser beneficioso xa que ofrece un nivel de flexibilidade ao programador que non está presente nas linguaxes estáticas. As linguaxes dinámicas adoitan ser máis lentas na execución que as súas contrapartes estáticas xa que implican un paso adicional para descubrir de forma dinámica a escritura de cada variable.

Parche de mono

A tipificación estática vs dinámica é un trazo fundamental nunha linguaxe de programación, ir cun paradigma sobre o outro pode permitir unha serie de patróns e prácticas diferentes que poden mellorar significativamente a calidade e a velocidade de desenvolvemento. Tamén pode abrir a porta a moitas limitacións e antipatróns se non se realizan consideracións coidadosas ao tomar decisións de deseño.

En particular, sábese que as linguaxes de programación de tipo dinámico ofrecen un maior nivel de flexibilidade xa que non restrinxen unha variable a un só tipo. Esta flexibilidade supón o custo dunha responsabilidade adicional para o programador ao implementar e depurar programas para asegurarse de que non se produzan comportamentos imprevisibles. O patrón de parche de mono provén desta filosofía.

Monkey Patch refírese ao proceso de estender/cambiar o funcionamento dun compoñente en tempo de execución. O compoñente en cuestión pode ser unha biblioteca, unha clase, un método ou mesmo un módulo. A idea é a mesma: un anaco de código faise para realizar unha determinada tarefa e o obxectivo do parche de mono é cambiar ou estender o comportamento dese anaco de código para que realice unha nova tarefa, todo sen cambiar o propio código. .

Isto é posible na linguaxe de programación dinámica xa que independentemente do tipo de compoñente que esteamos a tratar, aínda ten a mesma estrutura dun obxecto con diferentes atributos, os atributos poden albergar métodos que poden ser reasignados para lograr un novo comportamento no obxecto. sen entrar no seu interior e detalles de implementación. Isto vólvese especialmente útil no caso de bibliotecas e módulos de terceiros xa que adoitan ser máis difíciles de modificar.

O seguinte exemplo mostrará un caso de uso común que pode beneficiarse do uso da técnica de parche de mono. Javascript utilizouse para implementar aquí, pero isto aínda debería aplicarse en xeral a calquera outra linguaxe de programación dinámica.

Exemplo

Implementa un marco de proba mínimo co módulo HTTP nativo de Node

As probas unitarias e de integración poden caer dentro dos casos de uso do parche de Monkey. Normalmente implican casos de proba que abarcan máis dun servizo para probas de integración ou dependencias de API e/ou bases de datos para probas unitarias. Nestes dous escenarios, e para lograr os obxectivos das probas en primeiro lugar, queremos que as nosas probas sexan independentes destes recursos externos. O xeito de conseguilo é a través da burla. Mocking é simular o comportamento dos servizos externos para que a proba poida centrarse na lóxica real do código. O parche de mono pode ser útil aquí xa que pode modificar os métodos dos servizos externos substituíndoos por métodos de marcador de posición que chamamos "estub". Estes métodos devolven o resultado esperado nos casos de proba, polo que podemos evitar iniciar solicitudes aos servizos de produción só para probas.

O seguinte exemplo é unha implementación sinxela do parche de Monkey no módulo http nativo de NodeJ. O módulo http é a interface que implementa os métodos de protocolo http para NodeJs. Utilízase principalmente para crear servidores http barebone e comunicarse con servizos externos mediante o protocolo http.

No seguinte exemplo temos un caso de proba sinxelo no que chamamos a un servizo externo para obter a lista de ID de usuario. En lugar de chamar ao servizo real, parcheamos o método http get para que só devolva o resultado esperado que é unha matriz de identificadores de usuario aleatorios. Isto pode non parecer de gran importancia xa que só estamos a buscar datos, pero se implementamos outro caso de proba que implique alterar algún tipo de datos, poderemos alterar accidentalmente os datos de produción ao executar as probas.

Deste xeito podemos implementar as nosas funcionalidades e escribir probas para cada funcionalidade ao tempo que aseguramos a seguridade dos nosos servizos de produción.

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

O código anterior é sinxelo, importamos o módulo http, reasignamos o método http.get cun novo método que só devolve unha matriz de IDs. Agora chamamos ao novo método parcheado dentro do caso de proba e obtemos o novo resultado esperado.

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

Trampas e limitacións comúns

Non debería sorprender que o parche de mono teña os seus propios defectos e limitacións. No contexto dos módulos do sistema de módulos de nodos, parchear un módulo global como http considérase unha operación con efectos secundarios, porque a http é accesible desde calquera punto dentro da base de código e calquera outra entidade pode ter unha dependencia del. Estas entidades esperan que o módulo http funcione co seu comportamento habitual, ao cambiar un dos métodos http rompemos efectivamente todas as outras dependencias http dentro da base de código.

Dado que estamos operando nunha linguaxe de tipo dinámico, é posible que as cousas non fallen inmediatamente e prefiren un comportamento imprevisible que fai que a depuración sexa unha tarefa extremadamente complexa. Noutros casos de uso, pode haber dous parches diferentes do mesmo compoñente no mesmo atributo, nese caso non podemos prever realmente que parche terá prioridade sobre o outro, o que resultará nun código aínda máis imprevisible.

Tamén é importante mencionar que o parche de mono pode ter lixeiras variacións no comportamento entre as diferentes linguaxes de programación. Todo depende do deseño da lingua e das opcións de implementación. Por exemplo, en Python, non todas as instancias que usan un método parcheado se verán afectadas polo parche. Se unha instancia chama explícitamente ao método parcheado, entón obterá a nova versión actualizada, pola contra, outras instancias que só poden ter atributos que apunten ao método parcheado e que non o chamen explícitamente obterán a versión orixinal, isto débese a como python funciona a vinculación nas clases.

Conclusión

Neste artigo exploramos as distincións de alto nivel entre linguaxes de programación estáticas e dinámicas, vimos como as linguaxes de programación dinámicas poden beneficiarse de novos paradigmas e patróns que aproveitan a flexibilidade inherente que estas linguaxes ofrecen. O exemplo que mostramos estaba relacionado co parche de Monkey, unha técnica utilizada para estender o comportamento do código sen cambialo desde a fonte. Vimos un caso no que o uso desta técnica sería beneficioso xunto cos seus posibles inconvenientes. O desenvolvemento de software ten que ver con compensacións, e empregar a solución correcta para o problema require consideracións elaboradas do programador e unha boa comprensión dos principios e fundamentos da arquitectura.


Career Services background pattern

Servizos de Carreira

Contact Section background image

Mantémonos en contacto

Code Labs Academy © 2024 Todos os dereitos reservados.