Menu
Coddy logo textTech

Vue d’ensemble 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 lors de l’exécution. Cette approche vous offre la sécurité des types et de meilleures performances, tout en réduisant le code répétitif standard.

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, puis l’exécution de go generate lance 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 code Go ordinaire qui est compilé avec votre programme.

Les outils courants de génération de code dans l’écosystème Go incluent stringer pour les méthodes de conversion d’énumérations en chaînes, 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 fortement typées ou du code client.

Le principal avantage 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. En contrepartie, une étape de compilation supplémentaire est nécessaire, ainsi que des fichiers générés à gérer dans votre dépôt. Pour les modèles de POO 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 semblables à des énumérations ! Même si nous ne pouvons pas exécuter un véritable go generate dans cet environnement, vous simulerez ce que font les outils de génération de code : produire programmatiquement du code source Go à partir de 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 semblable à une énumération, avec un Name (chaîne) et des Values (slice de chaînes représentant les noms des constantes).

    Créez une fonction GenerateStringMethod(e EnumType) string qui produit du 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 à l'aide de 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 semblables à des énumérations.

    Lisez le nom du type d'énumération, puis lisez un nombre de valeurs suivi de chaque nom de valeur. Transmettez 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'énumération
  • Ligne 2 : nombre de valeurs
  • Lignes suivantes : chaque nom de valeur

Par exemple, avec :

Status
3
Pending
Active
Completed

Votre sortie doit ê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 avec :

Priority
2
Low
High

Votre sortie doit ê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 illustre le concept fondamental qui se trouve derrière des outils comme stringer : générer du code répétitif, mais sûr du point de vue des types, au moment de la compilation plutôt que d'utiliser la réflexion à l'exécution. Votre générateur produit du code Go valide qui pourrait être enregistré dans un fichier et compilé avec le reste d'un projet.

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 le nom de chaque 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

Entraînez-vous par vous-même : Compilateur Go en ligne