Skip to content
Menu
CDhistory
CDhistory

Vérification des méthodes Ruby à l’aide du module#prepend

Posted on décembre 12, 2021 by admin

Hier, je passais un entretien pour un poste de développeur Ruby dans une entreprise ici à Denver lorsque mon interlocuteur a posé un bon défi de métaprogrammation : il m’a demandé d’écrire un vérificateur de méthodes automatisé.

Il a décrit une méthode de classe qui prendrait le nom d’une méthode d’instance et générerait une trace écrite automatique et enregistrée chaque fois que cette méthode cible est appelée. Quelque chose comme ceci:

Ce qui produirait un résultat qui ressemblerait à ceci:

J’ai trouvé une solution qui fonctionnait mais qui impliquait des piratages potentiellement dangereux de Object#send. Avant de présenter ma solution, je me suis dit « c’est probablement une mauvaise idée, mais… » et j’avais raison. Les bonnes idées commencent rarement de cette façon.

La solution qu’il a proposée impliquait l’utilisation de la Module#prepend souvent négligée et j’ai pensé que c’était assez cool pour mériter un article.

La stratégie était d’utiliser le comportement de Module#prepend pour créer une méthode wrapper générée dynamiquement pour Messenger#share.

Dans ce wrapper, nous aurons un message « Performing » déclenché avant l’exécution du corps de la méthode, l’exécution de la méthode elle-même, puis un message final « Exiting » une fois l’exécution de la méthode terminée avec succès.

Mais pour comprendre comment utiliser Module#prepend pour faire cela, nous devons d’abord bien comprendre comment fonctionne l’héritage des méthodes en Ruby.

  • Chaînes d’ancêtres
  • Le module lui-même
  • Réunir tout ça
  • Conclusion

Chaînes d’ancêtres

Chaque objet Ruby se trouve à la fin de ce qu’on appelle une chaîne d’ancêtres. Il s’agit d’une liste des objets ancêtres dont l’objet hérite. Ruby utilise cette chaîne d’ancêtres pour déterminer quelle version d’une méthode (s’il y en a une) est exécutée lorsque l’objet reçoit un message.

Vous pouvez en fait visualiser l’arbre des ancêtres pour n’importe quel objet en appelant Module#ancestors. Par exemple, voici la chaîne d’ancêtres de notre classe Messenger:

Lorsque nous #include ou #prepend un module dans une classe, Ruby apporte des modifications à la chaîne d’ancêtres de cette classe, en tordant quelles méthodes sont trouvées et dans quel ordre. La grande différence entre #include et #prepend est l’endroit où ce changement est effectué. Créons un module appelé Auditable qui contiendra (éventuellement) le code qui fait notre magie d’audit:

Si nous utilisons Module#include pour importer les méthodes (actuellement inexistantes) de Auditable, Ruby serrera ce module comme un ancêtre de notre classe Messenger. Ici, voyez par vous-même:

Quand nous appelons une méthode sur Messenger, Ruby regardera ce qui est défini dans la classe Messenger et – s’il ne trouve pas de méthode correspondant à l’appel – remontera la chaîne des ancêtres jusqu’à Auditable pour chercher à nouveau. S’il ne trouve pas ce qu’il cherche sur Auditable, il passe à Object et ainsi de suite.

C’est #include. Si nous utilisons plutôt Module#prepend pour importer le contenu du module, nous obtenons un effet totalement différent:

#prepend fait de Messenger un ancêtre de Auditable. Attendez. Quoi?

On pourrait croire que Messenger est maintenant une superclasse de Auditable, mais ce n’est pas exactement ce qui se passe ici. Les instances de la classe Messenger sont toujours des instances de la classe Messenger mais Ruby va maintenant chercher les méthodes à utiliser pour Messenger dans le module Auditable avant de les chercher sur la classe Messenger elle-même.

Et ça, mes amis, c’est ce dont nous allons profiter pour construire cet auditeur : si nous créons une méthode appelée Auditable#share, Ruby va la trouver avant de trouver Messenger#share. Nous pouvons alors utiliser un appel super dans Auditable#share pour accéder (et exécuter !) la méthode originale définie sur Messenger.

Le module lui-même

Nous n’allons pas réellement créer une méthode appelée Auditable#share. Pourquoi pas ? Parce que nous voulons que ce soit un utilitaire flexible. Si nous codons en dur la méthode Auditable#share, nous ne pourrons l’utiliser que sur les méthodes qui implémentent la méthode #share. Ou pire, nous devrions réimplémenter ce même pattern d’auditeur pour chaque méthode que nous voudrons jamais auditer. Non merci.

Alors, à la place, nous allons définir notre méthode dynamiquement et la méthode de la classe audit_method pour la déclencher :

Lorsque audit_method est appelée dans une classe d’implémentation, une méthode est créée dans le module Auditable avec le même nom que la méthode à auditer. Dans notre cas, cela créera une méthode appelée Auditable#share. Comme nous l’avons mentionné, Ruby trouvera cette méthode avant de trouver la méthode originale sur Messenger parce que nous prépointerons le module Auditable dans la classe d’implémentation.

Cela signifie que nous pouvons utiliser un super appel pour remonter la chaîne des ancêtres et exécuter Messenger#send. Lorsque nous le faisons, nous passons les arguments que nous avons recueillis (*arguments) en haut de la chaîne aussi.

Une fois que nous avons appelé la méthode originale, nous imprimons notre message de sortie et appelons ça un jour. Bon travail, les gars!

Réunir tout ça

Maintenant, c’est juste une question de prependmettre ce module dans notre classe Messenger et nous devrions être prêts à partir:

Et bon sang, ça marche:

Les implications ici sont énormes pour l’audit, mais il y a plus à cette astuce. Vous pouvez utiliser la préposition pour changer le comportement des objets sans changer les objets eux-mêmes. C’est une façon adaptable et claire de créer des composants d’ordre supérieur en Ruby. Tests de performance, gestion des erreurs. Vous pouvez faire beaucoup de choses ici.

Conclusion

Les modules Ruby sont plus compliqués que la plupart des gens le pensent. Ce n’est pas aussi simple que de « déverser » du code d’un endroit à l’autre, et la compréhension de ces différences débloque des outils vraiment soignés pour votre ceinture utilitaire.

Vous avez peut-être remarqué que je n’ai parlé que de Module#include et Module#prepend aujourd’hui, et que je n’ai pas touché à Module#extend. C’est parce qu’il fonctionne très différemment de ses cousins. Je rédigerai bientôt une explication approfondie de Module#extend pour compléter l’ensemble.

Pour l’instant, si vous voulez en savoir plus, je vous recommande de lire Ruby modules : Include vs Prepend vs Extend par Léonard Hetsch. Il a été vraiment utile pour mettre tout cela ensemble.

Laisser un commentaire Annuler la réponse

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Articles récents

  • Acela est de retour : NYC ou Boston pour 99 $
  • Entrée OMIM – # 608363 – SYNDROME DE DUPLICATION DU CHROMOSOME 22q11.2
  • Les parents de Kate Albrecht – En savoir plus sur son père Chris Albrecht et sa mère Annie Albrecht
  • Temple Fork Outfitters
  • Burr (roman)

Archives

  • février 2022
  • janvier 2022
  • décembre 2021
  • novembre 2021
  • octobre 2021
  • septembre 2021
  • août 2021
  • juillet 2021
  • juin 2021
  • mai 2021
  • avril 2021
  • DeutschDeutsch
  • NederlandsNederlands
  • SvenskaSvenska
  • DanskDansk
  • EspañolEspañol
  • FrançaisFrançais
  • PortuguêsPortuguês
  • ItalianoItaliano
  • RomânăRomână
  • PolskiPolski
  • ČeštinaČeština
  • MagyarMagyar
  • SuomiSuomi
  • 日本語日本語
©2022 CDhistory | Powered by WordPress & Superb Themes