Aperçu de la génération de code
Fait partie de la section Programmation Orientée Objet du Journey GO de Coddy — leçon 84 sur 107.
La génération de code est une alternative à la réflexion qui crée du code source Go au moment de la compilation plutôt que d'inspecter les types au moment de l'exécution. Cette approche vous offre une sécurité de typage et de meilleures performances tout en réduisant le code répétitif (boilerplate).
Go fournit la commande go generate pour automatiser la génération de code. Vous placez une directive de commentaire spéciale dans votre fichier source, et l'exécution de go generate exécute l'outil spécifié :
//go:generate stringer -type=Status
type Status int
const (
Pending Status = iota
Active
Completed
)L'exécution de go generate ./... invoque l'outil stringer, qui crée un nouveau fichier contenant une méthode String() pour le type Status. Le code généré est du Go standard qui est compilé avec votre programme.
Les outils de génération de code courants dans l'écosystème Go incluent stringer pour les méthodes de chaînes d'énumération, mockgen pour créer des mocks de test à partir d'interfaces, et protoc pour générer du code à partir de définitions Protocol Buffer. De nombreux ORM et frameworks d'API utilisent également la génération pour créer des requêtes de base de données de type sécurisé ou du code client.
L'avantage clé par rapport à la réflexion est que le code généré est vérifié par le compilateur. Les erreurs sont détectées au moment de la compilation, et non à l'exécution. Le compromis réside dans une étape de construction supplémentaire et des fichiers générés à gérer dans votre dépôt. Pour les modèles de programmation orientée objet (OOP) nécessitant des implémentations répétitives sur de nombreux types, la génération de code offre souvent le meilleur équilibre entre sécurité et commodité.
Défi
FacileConstruisons un générateur de code qui crée des méthodes String() pour des types de type enum ! Bien que nous ne puissions pas exécuter un véritable go generate dans cet environnement, vous allez simuler ce que font les outils de génération de code — produire du code source Go par programmation basé sur des définitions de types.
Vous organiserez votre code sur deux fichiers :
generator.go: Créez votre logique de génération de code.Construisez une structure
EnumTypequi représente une définition de type de type enum avec unName(string) et desValues(slice de strings représentant les noms des constantes).Créez une fonction
GenerateStringMethod(e EnumType) stringqui produit le code source Go pour une méthodeString(). Le code généré doit suivre ce modèle :func (e TypeName) String() string { switch e { case Value1: return "Value1" case Value2: return "Value2" default: return "Unknown" } }Créez également une fonction
GenerateConstants(e EnumType) stringqui produit les déclarations de constantes en utilisantiota:const ( Value1 TypeName = iota Value2 Value3 )Enfin, créez
GenerateFullCode(e EnumType) stringqui combine les deux sorties avec un commentaire d'en-tête indiquant qu'il s'agit de code généré :// Code generated by enum generator. DO NOT EDIT. [constants] [string method]main.go: Utilisez votre générateur pour produire du code pour différents types d'enum.Lisez le nom du type d'enum, puis lisez le nombre de valeurs suivi de chaque nom de valeur. Passez ces informations à votre générateur et affichez le code généré complet.
Les entrées suivantes seront fournies :
- Ligne 1 : Nom du type d'enum
- Ligne 2 : Nombre de valeurs
- Lignes suivantes : Chaque nom de valeur
Par exemple, étant donné :
Status
3
Pending
Active
CompletedVotre sortie devrait être :
// Code generated by enum generator. DO NOT EDIT.
const (
Pending Status = iota
Active
Completed
)
func (e Status) String() string {
switch e {
case Pending:
return "Pending"
case Active:
return "Active"
case Completed:
return "Completed"
default:
return "Unknown"
}
}Et étant donné :
Priority
2
Low
HighVotre sortie devrait être :
// Code generated by enum generator. DO NOT EDIT.
const (
Low Priority = iota
High
)
func (e Priority) String() string {
switch e {
case Low:
return "Low"
case High:
return "High"
default:
return "Unknown"
}
}Cet exercice démontre le concept de base derrière des outils comme stringer — générer du code répétitif mais sûr au niveau des types au moment de la compilation plutôt que d'utiliser la réflexion au moment de l'exécution. Votre générateur produit du code Go valide qui pourrait être sauvegardé dans un fichier et compilé avec le reste d'un projet.
Aide-mémoire
La génération de code crée du code source Go au moment de la compilation plutôt que d'inspecter les types au moment de l'exécution, offrant ainsi une sécurité de type et de meilleures performances tout en réduisant le code répétitif.
La commande go generate automatise la génération de code à l'aide de directives de commentaires spéciales :
//go:generate stringer -type=Status
type Status int
const (
Pending Status = iota
Active
Completed
)L'exécution de go generate ./... invoque l'outil spécifié (comme stringer), qui crée un nouveau fichier contenant le code généré qui sera compilé avec votre programme.
Les outils de génération de code courants incluent :
stringer- génère des méthodesString()pour les types énumérésmockgen- crée des doublons de test (mocks) à partir d'interfacesprotoc- génère du code à partir de définitions Protocol Buffer
Principaux avantages par rapport à la réflexion :
- Le code généré est vérifié par le compilateur
- Les erreurs sont détectées au moment de la construction, pas à l'exécution
- Meilleures performances
Les compromis incluent une étape de construction supplémentaire et des fichiers générés à gérer dans votre dépôt.
Essayez vous-même
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Lire le nom du type enum
typeName, _ := reader.ReadString('\n')
typeName = strings.TrimSpace(typeName)
// Lire le nombre de valeurs
countStr, _ := reader.ReadString('\n')
count, _ := strconv.Atoi(strings.TrimSpace(countStr))
// Lire chaque nom de valeur
values := make([]string, count)
for i := 0; i < count; i++ {
value, _ := reader.ReadString('\n')
values[i] = strings.TrimSpace(value)
}
// TODO: Créer un EnumType avec les données lues
// TODO: Générer et afficher le code complet en utilisant GenerateFullCode
}
Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.
Toutes les leçons de Programmation Orientée Objet
1Fondamentaux de la POO en Go
Fichiers externesEspace de travail et Modules GoPackages et ImportsNoms exportés vs non exportésIntroduction à la POO en GoLes Structs comme ClassesDéfinir des méthodes sur les StructsReceveurs par pointeur vs par valeurInitialisation de StructsFonctions constructeursRécapitulatif - Calculatrice simple4Interfaces
Introduction aux interfacesImplémentation impliciteL'interface comme contratInterface vide (any)Assertion de typeType SwitchComposition d'interfacesInterfaces Stringer & ErrorRécapitulatif - Calculateur de formes7Encapsulation
Champs exportés vs non exportésEncapsulation au niveau du packageMéthodes Getter & SetterMasquage d'informations en GoRécapitulatif - Dossiers étudiants10Génériques (Go 1.18+)
Introduction aux génériquesParamètres de typeContraintes de typeStructures génériquesContournement pour les méthodes génériquesRécapitulatif - Collections génériques13Patrons de conception, Partie 1
Introduction aux Design PatternsPatron SingletonPatron FactoryPatron Abstract FactoryPatron ObserverPatron Strategy2Exploration approfondie des Types et Structs
Types de base et compositesDéfinitions de types personnalisésTags de StructStructs anonymesStructs imbriquéesValeurs par défaut et Zero ValuesRécapitulatif - Carnet de contacts5Composition plutôt qu'héritage
Pourquoi Go n'a pas d'héritageLes bases de l'imbrication de structsPromotion de méthodesImbriquer plusieurs structsImbrication vs AgrégationMasquage de méthodes imbriquéesRécapitulatif - Hiérarchie des employés8Gestion des erreurs & POO
L'interface errorTypes d'erreurs personnalisésError Wrapping (fmt.Errorf)Erreurs sentinelleserrors.Is() et errors.As()Panic, Defer et RecoverRécapitulatif - File Parser11Bibliothèque standard et POO
io.Reader & io.WriterL'interface sort.InterfaceL'interface fmt.Stringerencoding/json avec les StructsL'interface http.HandlerRécapitulatif - Modèles d'API REST14Patrons de conception, Partie 2
Patron CommandePatron AdaptateurPatron DécorateurPatron Template MethodPatron ÉtatPatron CompositeMiddleware en tant que Décorateur3Pointeurs et Mémoire
Bases des pointeurs en GoPointeurs vers des structsPassage par valeur vs référenceLa fonction new()Garbage Collection en GoRécapitulatif - Constructeur de liste chaînée6Le polymorphisme en Go
Le polymorphisme via les interfacesLe Duck Typing en GoRègles de satisfaction des interfacesCollections polymorphesInjection de dépendancesRécapitulatif - Processeur de paiement9Concurrence et POO
Bases des GoroutinesChannels et CommunicationChannels avec buffer vs sans bufferL'instruction Selectsync.Mutex et sync.RWMutexsync.WaitGroupConception de structures Thread-SafeRécapitulatif - Worker Pool12Concepts POO Avancés
Pattern Functional OptionsPattern Builder en GoChaînage de méthodesAlias de types vs DéfinitionsBases de la RéflexionAperçu de la génération de code