Menu
Coddy logo textTech

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é.

challenge icon

Défi

Facile

Construisons 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 EnumType qui représente une définition de type de type enum avec un Name (string) et des Values (slice de strings représentant les noms des constantes).

    Créez une fonction GenerateStringMethod(e EnumType) string qui produit le code source Go pour une méthode String(). 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) string qui produit les déclarations de constantes en utilisant iota :

    const (
        Value1 TypeName = iota
        Value2
        Value3
    )

    Enfin, créez GenerateFullCode(e EnumType) string qui 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
Completed

Votre 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
High

Votre 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éthodes String() pour les types énumérés
  • mockgen - crée des doublons de test (mocks) à partir d'interfaces
  • protoc - 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
}
quiz iconTestez-vous

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