Postulez à notre nouveau Data Science & AI et Cybersecurity Cohortes à temps partiel

Monkey Patching dans les langages de programmation dynamiques : un exemple JavaScript

Javascript
programmation dynamique
Monkey Patching dans les langages de programmation dynamiques cover image

Introduction

Cet article explorera les concepts des langages de programmation dynamique et statique, les principales différences entre les deux et ce que chaque paradigme offre en termes d'avantages et de pièges. Cette exploration se concentrera davantage sur les langages de programmation dynamiques, en particulier l'un des modèles essentiels qu'ils permettent : Monkey Patch, ce modèle sera présenté à l'aide d'un exemple en JavaScript.

Langages de programmation dynamiques ou statiques

Terminologie

Afin de comprendre ce qui constitue un langage dynamique ou statique, nous devons comprendre quelques termes clés couramment utilisés dans ce contexte : Temps de compilation, Runtime et *Vérification de type. *.

Compile et Runtime sont deux termes qui correspondent à différentes étapes du cycle de vie d'un programme informatique, à commencer par le moment de la compilation.

Temps de compilation

Le temps de compilation est la première étape du cycle de vie d'un programme. Un développeur écrit du code dans un langage de programmation donné. Le plus souvent, la machine est incapable de comprendre le code écrit dans un langage de haut niveau. Un compilateur dédié est donc utilisé pour le traduire dans un format intermédiaire de niveau inférieur qui est prêt à être exécuté.

Durée

Le runtime encapsule généralement deux étapes : charger le programme en mémoire en allouant les ressources nécessaires à son exécution ainsi que ses instructions, puis exécuter le programme en suivant l'ordre de ces instructions.

Le diagramme suivant illustre ce processus :

Vérification des types

La vérification de type est une fonctionnalité intégrée dans presque tous les langages de programmation. C'est la capacité de vérifier si une valeur attribuée à une variable donnée correspond au type correct de cette variable. Chaque langage de programmation a une manière différente de représenter une valeur d'un type donné en mémoire. Ces différentes représentations permettent de vérifier la correspondance entre le type d'une valeur et le type d'une variable à laquelle on tente d'attribuer cette valeur.

Maintenant que nous avons une compréhension approfondie du cycle de vie d’un programme et de la vérification de type, nous pouvons procéder à l’exploration des langages de programmation statiques.

Langages de programmation statiques

Les langages de programmation statiques, également appelés langages typés statiquement, sont des langages qui appliquent la vérification de type que nous avons mentionnée lors de la phase de compilation. Cela signifie effectivement qu'une variable conserve son type de déclaration et qu'aucune valeur ne peut lui être attribuée autre que les valeurs de son type de déclaration. Les langages de programmation statiques offrent une sécurité supplémentaire lorsqu'il s'agit de types, mais peuvent ralentir le processus de développement dans certains cas d'utilisation lorsque cela devient une restriction stricte.

Langages de programmation dynamique

Les langages de programmation dynamique, en revanche, appliquent la vérification de type au moment de l'exécution. Cela signifie que n'importe quelle variable peut contenir n'importe quelle valeur à tout moment du programme. Cela peut être bénéfique car cela offre au développeur un niveau de flexibilité qui n’est pas présent dans les langages statiques. Les langages dynamiques ont tendance à être plus lents à s'exécuter que leurs homologues statiques car ils impliquent une étape supplémentaire de détermination dynamique du typage de chaque variable.

Patch de singe

La saisie statique ou dynamique est une caractéristique fondamentale d'un langage de programmation. Choisir un paradigme plutôt qu'un autre peut permettre une multitude de modèles et de pratiques différents qui peuvent améliorer considérablement la qualité et la vitesse de développement. Cela peut également ouvrir la porte à de nombreuses limitations et anti-modèles si aucune considération minutieuse n’est prise lors de la prise de décisions de conception.

En particulier, les langages de programmation typés dynamiquement sont connus pour offrir un niveau plus élevé de flexibilité puisqu'ils ne limitent pas une variable à un seul type. Cette flexibilité s'accompagne d'un coût supplémentaire pour le développeur lors de la mise en œuvre et du débogage des programmes afin de s'assurer qu'aucun comportement imprévisible ne se produit. Le motif patch singe vient de cette philosophie.

Monkey Patch fait référence au processus d'extension/de modification du fonctionnement d'un composant au moment de l'exécution. Le composant en question peut être une bibliothèque, une classe, une méthode ou encore un module. L'idée est la même : un morceau de code est conçu pour accomplir une certaine tâche, et le but du patching singe est de modifier ou d'étendre le comportement de ce morceau de code afin qu'il accomplisse une nouvelle tâche, le tout sans changer le code lui-même. .

Ceci est rendu possible dans un langage de programmation dynamique puisque quel que soit le type de composant auquel nous avons affaire, il a toujours la même structure d'objet avec des attributs différents, les attributs peuvent contenir des méthodes qui peuvent être réaffectées pour obtenir un nouveau comportement dans l'objet. sans entrer dans ses composants internes et les détails de sa mise en œuvre. Cela devient particulièrement utile dans le cas de bibliothèques et de modules tiers, car ceux-ci ont tendance à être plus difficiles à modifier.

L'exemple suivant présentera un cas d'utilisation courant qui peut bénéficier de l'utilisation de la technique du patch singe. Javascript a été utilisé ici dans un souci d'implémentation, mais cela devrait encore s'appliquer largement à tout autre langage de programmation dynamique.

Exemple

Implémentez un cadre de test minimal avec le module HTTP natif de Node

Les tests unitaires et d'intégration peuvent relever des cas d'utilisation des correctifs Monkey. Ils impliquent généralement des cas de test qui couvrent plusieurs services pour les tests d'intégration, ou des dépendances d'API et/ou de base de données pour les tests unitaires. Dans ces deux scénarios, et afin d'atteindre les objectifs des tests en premier lieu, nous souhaiterions que nos tests soient indépendants de ces ressources externes. La façon d’y parvenir est de se moquer. La simulation consiste à simuler le comportement de services externes afin que le test puisse se concentrer sur la logique réelle du code. Le patching Monkey peut être utile ici car il peut modifier les méthodes des services externes en les remplaçant par des méthodes d'espace réservé que nous appelons « stub ». Ces méthodes renvoient le résultat attendu dans les cas de test afin que nous puissions éviter de lancer des requêtes aux services de production uniquement pour le plaisir des tests.

L'exemple suivant est une implémentation simple du correctif Monkey sur le module http natif de NodeJs. Le module http est l'interface qui implémente les méthodes du protocole http pour NodeJs. Il est principalement utilisé pour créer des serveurs http barebone et communiquer avec des services externes via le protocole http.

Dans l'exemple ci-dessous, nous avons un cas de test simple dans lequel nous appelons un service externe pour récupérer la liste des identifiants utilisateur. Plutôt que d'appeler le service réel, nous corrigeons la méthode http get afin qu'elle renvoie simplement le résultat attendu qui est un tableau d'identifiants d'utilisateur aléatoires. Cela peut ne pas sembler d'une grande importance puisque nous récupérons simplement des données, mais si nous implémentons un autre scénario de test impliquant une modification de données, nous pourrions accidentellement modifier les données de production lors de l'exécution de tests.

Nous pouvons ainsi implémenter nos fonctionnalités, et rédiger des tests pour chaque fonctionnalité tout en assurant la sécurité de nos services de production.

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

Le code ci-dessus est simple, nous importons le module http, réaffectons la méthode http.get avec une nouvelle méthode qui renvoie simplement un tableau d'identifiants. Nous appelons maintenant la nouvelle méthode patchée dans le scénario de test et nous obtenons le nouveau résultat attendu.

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

Pièges et limites courants

Il n’est pas surprenant que les correctifs singe aient leurs propres défauts et limites. Dans le contexte des modules du système de modules de nœuds, patcher un module global tel que http est considéré comme une opération avec des effets secondaires, car http est accessible à partir de n'importe quel point de la base de code et toute autre entité peut en dépendre. Ces entités s'attendent à ce que le module http fonctionne selon son comportement habituel, en modifiant l'une des méthodes http, nous cassons efficacement toutes les autres dépendances http à l'intérieur de la base de code.

Puisque nous opérons dans un langage typé dynamiquement, les choses pourraient ne pas échouer immédiatement et préféreraient adopter par défaut un comportement imprévisible, ce qui rend le débogage une tâche extrêmement complexe. Dans d'autres cas d'utilisation, il peut y avoir deux correctifs différents du même composant sur le même attribut, auquel cas nous ne pouvons pas vraiment prédire quel correctif aura priorité sur l'autre, ce qui entraînera un code encore plus imprévisible.

Il est également important de mentionner que les correctifs singe peuvent avoir de légères variations de comportement entre les différents langages de programmation. Tout dépend de la conception du langage et des choix d'implémentation. Par exemple, en python, toutes les instances utilisant une méthode patchée ne seront pas affectées par le patch. Si une instance appelle explicitement la méthode corrigée, elle obtiendra la nouvelle version mise à jour. Au contraire, d'autres instances qui pourraient uniquement avoir des attributs pointant vers la méthode corrigée et ne l'appelant pas explicitement obtiendront la version originale, cela est dû à la façon dont python la liaison dans les classes fonctionne.

Conclusion

Dans cet article, nous avons exploré les distinctions de haut niveau entre les langages de programmation statiques et dynamiques. Nous avons vu comment les langages de programmation dynamiques peuvent bénéficier de nouveaux paradigmes et modèles tirant parti de la flexibilité inhérente qu'offrent ces langages. L'exemple que nous avons présenté était lié au patching Monkey, une technique utilisée pour étendre le comportement du code sans le modifier depuis la source. Nous avons vu un cas où l’utilisation de cette technique serait bénéfique ainsi que ses inconvénients potentiels. Le développement de logiciels est une question de compromis, et l'emploi de la bonne solution au problème nécessite des considérations élaborées de la part du développeur et une bonne compréhension des principes et principes fondamentaux de l'architecture.


Career Services background pattern

Services de carrière

Contact Section background image

Restons en contact

Code Labs Academy © 2024 Tous droits réservés.