Réflexion et introspection en Go : comment regarder sous le capot de votre code
\n\nImaginez que vous êtes un mécanicien qui peut non seulement remplacer une pièce dans le moteur, mais aussi regarder à l'intérieur, comprendre comment elle est conçue, et même modifier son comportement à la volée. C'est exactement le super-pouvoir que la réflexion et l'introspection en Go donnent au développeur. Cela semble magique ? En réalité, c'est un outil puissant qui est à la base de nombreuses bibliothèques, systèmes ORM et frameworks.
\n\nDans cet article, nous ne nous contenterons pas de discuter de la théorie. Nous écrirons du code réel qui montrera comment utiliser reflect pour analyser des structures, créer des fonctions universelles et même « espionner » les types à l'exécution. Prêt à améliorer vos compétences Go ? C'est parti !
Qu'est-ce que la réflexion et l'introspection ? Différence des concepts
\n\nAvant de plonger dans le code, distinguons clairement deux concepts similaires mais différents :
\n\n- \n
- L'introspection — c'est la capacité d'un programme à « se regarder lui-même » : connaître le type d'une variable, la structure d'un objet, la liste des méthodes ou des champs. C'est une analyse passive. \n
- La réflexion — c'est la capacité non seulement de regarder, mais aussi de modifier le comportement du programme pendant l'exécution : appeler dynamiquement des méthodes, créer des instances de types, modifier les valeurs des champs. \n
En Go, ces deux capacités sont gérées par le package reflect. Il donne au développeur des outils qui brisent le typage statique du langage, mais de manière contrôlée et sécurisée (si l'on sait ce que l'on fait).
Premier pas : utilisation de base de reflect.TypeOf et reflect.ValueOf
\n\nLa façon la plus simple de commencer est d'obtenir des informations sur n'importe quelle variable. Les deux « piliers » de la réflexion en Go sont les fonctions reflect.TypeOf() et reflect.ValueOf().
Exemple 1 : Connaître le type et la valeur
\n\npackage main\n\nimport (\n "fmt"\n "reflect"\n)\n\nfunc main() {\n name := "PythonLib"\n age := 42\n price := 99.99\n\n // Introspection : regarder les types\n fmt.Println("Type de name:", reflect.TypeOf(name))\n fmt.Println("Type de age:", reflect.TypeOf(age))\n fmt.Println("Type de price:", reflect.TypeOf(price))\n\n // Obtenir la valeur et son genre (Kind)\n v := reflect.ValueOf(price)\n fmt.Printf("Valeur : %v, Genre : %v\\n", v, v.Kind())\n}\n\nSortie :\n\nType de name: string\nType de age: int\nType de price: float64\nValeur : 99.99, Genre : float64\n
Notez v.Kind(). En Go, il y a une différence entre le type (type) et le genre (kind). Par exemple, si vous déclarez type MyInt int, alors TypeOf() retournera main.MyInt, tandis que Kind() retournera int. Ce détail est crucial lors de l'écriture de fonctions génériques.
Travail avec les structures : introspection des champs et des tags
\n\nL'une des tâches les plus courantes où la réflexion en Go est nécessaire est l'analyse des structures. Par exemple, vous écrivez votre propre ORM ou validateur qui doit « comprendre » quels champs une structure possède et quels sont leurs tags.
\n\nExemple 2 : Analyser une structure en détail
\n\ntype User struct {\n ID int `json:"id" validate:"required"`\n Name string `json:"name" validate:"min=3,max=50"`\n Email string `json:"email" validate:"email"`\n}\n\nfunc inspectStruct(s interface{}) {\n t := reflect.TypeOf(s)\n v := reflect.ValueOf(s)\n\n // Assurons-nous que c'est une structure\n if t.Kind() == reflect.Ptr {\n t = t.Elem()\n v = v.Elem()\n }\n\n if t.Kind() != reflect.Struct {\n fmt.Println("Erreur : une structure est attendue")\n return\n }\n\n fmt.Printf("Analyse de la structure : %s\\n", t.Name())\n fmt.Println("Champs :")\n \n for i := 0; i < t.NumField(); i++ {\n field := t.Field(i)\n value := v.Field(i)\n\n fmt.Printf(" %d. %s (%s) = %v\\n", i+1, field.Name, field.Type, value.Interface())\n \n