Comprendre la réflexion, l’introspection et l’intercession

Beaucoup de développeurs se sont un jour posé la question :

Comment puis-je afficher ou affecter la valeur d’une variable grâce à son nom stocké dans une variable de type string

Sans le savoir, ils ont par cette simple question posé un premier pas dans le monde de la méta-programmation et plus précisément, dans ce cas de figure, dans le monde de la réflexion. Bien sûr elle ne doit pas être confondue avec la réflexion issue de la pensée et de l’esprit, il s’agit ici de la réflexion telle qu’on pourrait la retrouver lorsqu’un miroir nous renvoie notre image.

La réflexion est un ensemble de mécanismes qui permettent à un programme informatique de s’examiner lui même ou bien de modifier son état ou sa structure au moment de son exécution. Elle s’inscrit au sein du paradigme de méta-programmation – méta qui signifie au dessus, au delà, il s’agit donc de programmation au dessus de la programmation.

Les deux mécanismes qui composent la réflexion sont nommés l’introspection et l’intercession. Derrière ces termes barbares, rien de compliqué : l’introspection concerne la capacité d’un programme à s’examiner, tandis que l’intercession concerne la capacité d’un programme à se modifier.

Un des intérêts majeur de la réflexion est qu’elle permet dans certains cas de réduire considérablement le nombre de lignes de code grâce à la généricité qu’elle induit. Elle permet aussi de programmer certains processus variables non connus par avance qui seraient difficilement programmables sans ce mécanisme. Finalement cette technique a aussi l’atout de supprimer des suites de conditions potentiellement extensibles qui rendrait vite le programme illisible et non maintenable.

Bien sûr, l’utilisation d’un tel mécanisme à un coût en terme de performance et l’exploiter massivement à tort et à travers n’est pas une option envisageable. Il ne s’agit pas non plus d’une solution miracle à tous les problèmes (voir anti-pattern marteau doré), il s’agit plutôt d’un dernier recours en cas d’impossibilité d’automatiser un processus autrement que par son utilisation où bien de vouloir appliquer un algorithme concis pour des raisons de lisibilité ou de maintenabilité.

On distingue la réflexion qui a lieu à l’exécution du templating qui a lieu à la compilation.

Que permettent concrètement ces techniques ?

Elles permettent par le biais de l’introspection de récupérer absolument toutes les informations sur la structure d’un programme et sur son état au moment de l’exécution. Il est donc possible de récupérer les noms, les valeurs, les types et l’ensemble des métadonnées des classes, des structures, des fonctions, des méthodes, et en programmation orientée objet, des propriétés, des attributs, des champs d’une classe, etc.

Sans le savoir, de nombreux développeurs utilisent régulièrement l’introspection de type. Il s’agit, pour citer quelques exemples, du fameux typeof GetType de C#, du instanceof ou du get_class de Php, du typeof de javascript ou encore du isKindOfClass d’Objective-C.

On reconnaitra la réfléxion par le fait qu’il est possible de récupérer ou bien d’affecter une propriété dont le nom est stocké dans une variable de type chaîne de caractères. La valeur de cette variable peut varier au cours de l’exécution du programme et ainsi permettre à une portion de code d’adopter un comportement générique.

Ci-dessous, une liste des cas les plus utiles.

Récupération et affectation de propriétés à partir d’une chaîne de caractères

Javascript

PHP

C#

Objective-C

 Instanciation de classe à partir d’une chaîne de caractères

On peut de la même manière instancier une classe à partir de son nom stocké dans une chaîne de caractères.

Javascript

PHP

C#

Java

Objective-C

 Invocation d’une méthode à partir d’une chaîne de caractères

Javascript

PHP

C#

Génération de code à la volée

L’injection de code à la volée commence à rentrer dans le cas extrême où le programme s’injecte lui même des portions de code en vue de permettre une spécification, une réorganisation ou une évolution. C’est l’intercession ultime. L’injection de code à la volée est sûrement une des techniques les plus lentes de la réflexion et pour ainsi dire trouver des exemples légitimes qui l’utilise est difficile. Cependant, nous ne rentrerons pas dans les détails tant cette technique diffère énormément d’un langage à un autre.

Que faire avec ?

Les systèmes de mapping avec le parcours des propriétés

Le mapping d’un résultat vers un objet et un bon exemple pour montrer comment la réflexion permet de diminuer le nombre de lignes de code et rendre le programme plus maintenable.

Javascript

 this[element] permet d’obtenir une propriété de l’objet sous la forme this[« proprerty »] à la place de this.property.

C#

On obtient ici le même résultat avec et sans la réflexion. On note cependant que la réflexion permet à l’objet d’être étendu au niveau de ses propriétés sans modifier l’implémentation de la fonction de mapping d’où l’intérêt de son utilisation.

Un tel processus pourrait être remplacé par du templating (ce qui reste de la méta-programmation) si la personne garde le contrôle sur son code source et sur sa compilation (ce qui n’est plus le cas lors de la définition de librairies ou d’API), et ce serait dans ce cas plus judicieux car plus optimisé.

Les Factory avec l’instanciation d’objets

L’instantiation d’objet dans un factory permet de montrer rapidement un cas de suppression de condition.

Chargement d’un plugin ou d’un module externe

Le chargement d’un plugin externe va nécessiter, dans certains langages, la réflexion afin de pouvoir instancier les classes internes au plugin. Un exemple est présent dans l’article suivant : Implémentation d’un systéme de plugin en C#.

Les génériques et l’instanciation de classe

Dans les langages statiquement typés qui permettent la définition de méthodes génériques, il est possible d’instancier une classe à la volée sans avoir à connaitre son type par avance. Cela permet de passer une classe en paramètre (paramètre de type) de fonction plutôt qu’une instance de classe (un objet).

C#


La fonction Run générique – c’est à dire qu’elle accepte un paramètre de type qui est ici en l’occurrence T – va pouvoir instancier la classe passée en paramètre de type. Dans cet exemple, la classe passée en paramètre de type doit implémenter l’interface IRunnable pour respecter la contrainte where T : IRunnable.

Ci-dessous l’utilisation :

C#

Il est ainsi possible de passer en paramètre de type de la méthode toute classe qui implémente l’interface IRunnable, le programme sera capable de l’instancier sans ajouter la moindre ligne de code supplémentaire. Ce qui signifie que le code de la classe Process peut être clôturé et fermé tout en conservant un fonctionnement général, ce qui est pratique pour le développement de librairies.

La lecture d’un fichier template ou de configuration

Voici un exemple où la réflexion trouve la place la plus légitime. Il s’agit ici de dicter les actions du programme par le biais d’un fichier externe. Cela peut servir dans de nombreux cas. Un exemple intéressant est celui de la création d’un moteur de template.

 

Le routage générique pour une Api Rest avec l’invocation de fonction

L’appel de fonction est aussi une fonctionnalité permise par la réflexion assez intéressante. En effet dans le cas d’une Api Rest ou le routage s’effectue avec grâce a un url codé par une chaîne de caractères, il peut être utile en terme de généricité de faire appel à l’invocation de méthode par le biais de la réflexion.

Bien sûr, il faut bien faire attention à ce que ceci ne constitue par une faille de sécurité, car autoriser l’appel de n’importe quelle fonction grâce à son nom et ses paramètres permettrait à n’importe qui d’exécuter très facilement n’importe quelle partie de code publiquement accessible.

Par exemple, il serait possible de proposer une url au format suivant :

http://www.monsite.fr/invoke/nomClasse/nomMethode/param1/param2/…

Ci-dessous exemple de l’invocation d’une méthode dynamiquement à partir d’une chaîne de caractères en Php (Routage avec framework Slim) :

On voit ici que l’intérêt de la réflexion se trouve dans le fait de ne pas devoir explicitement déclarer chaque routage séparément des uns des autres lors de l’ajout d’une fonctionnalité comme ci-dessous :

Et elle permet d’éviter un unique routage avec une suite de condition extensible comme ci-dessous :

Un système orienté aspect avec l’injection de code à la volée (cas extrême)

Cette technique pourrait par exemple être utilisée dans l’écriture d’un système orienté aspect AOP [en] où les points de jointure (join point) seraient insérés à la volée afin de permettre l’exécution du code d’aspect selon une condition particulière.

Compilation / Création de code à la volée (cas super extrême)

Poussé à son paroxysme, la réflexion permet à un programme d’en écrire un autre et de le compiler pendant son exécution. Cette fonction peut être fournie, soit à travers une API, soit directement par l’appel du compilateur. Sincèrement, il faut vraiment se trouver dans un cas particulier pour avoir à l’utiliser, par exemple écrire un évaluateur de code ou un langage de programmation particulier.


Ces exemples permettent de comprendre dans quels cas la réflexion peut être utile, mais le champ d’application est bien plus vaste que ceux présentés ci-dessus. C’est au développeur de définir précisément si le besoin de son utilisation se fait ressentir et si elle apporte quelque chose de positif dans le développement supérieur aux aspects négatifs qu’elle provoque tel que la perte de performances. Il est aussi possible d’obtenir dans certains cas un résultat quasi-équivalent sans y faire appel, notamment grâce au templating (génération de code à la compilation) où encore par la définition de paires clé / valeur au sein de dictionnaires qui permettent de définir une sorte de configuration.

J’espère que cet article vous aura permis de comprendre un peu mieux la réflexion ou bien d’approfondir vos connaissances préalables. En tout cas si vous l’avez compris, vous verrez assez vite que ces techniques permettent tellement de faire le café que vous ne pourrez plus vous en passer… et c’est bien ça, le piège de la réflexion.


Laisser un commentaire