Menu
Coddy logo textTech

Überblick über Codegenerierung

Teil des Abschnitts Objektorientierte Programmierung der GO-Journey von Coddy. Lektion 84 von 107.

Codegenerierung ist eine Alternative zur Reflection, die Go-Quellcode zur Kompilierzeit erstellt, anstatt Typen zur Laufzeit zu untersuchen. Dieser Ansatz bietet dir Typsicherheit und eine bessere Leistung und reduziert gleichzeitig sich wiederholenden Boilerplate-Code.

Go stellt den Befehl go generate bereit, um die Codegenerierung zu automatisieren. Du platzierst eine spezielle Kommentardirektive in deiner Quelldatei, und beim Ausführen von go generate wird das angegebene Tool ausgeführt:

//go:generate stringer -type=Status

type Status int

const (
    Pending Status = iota
    Active
    Completed
)

Das Ausführen von go generate ./... ruft das Tool stringer auf, das eine neue Datei mit einer String()-Methode für den Typ Status erstellt. Der generierte Code ist gewöhnlicher Go-Code, der zusammen mit deinem Programm kompiliert wird.

Zu den gängigen Werkzeugen zur Codegenerierung im Go-Ökosystem gehören stringer für String-Methoden von Enumerationen, mockgen zum Erstellen von Test-Mocks aus Interfaces und protoc zum Generieren von Code aus Protocol-Buffer-Definitionen. Viele ORMs und API-Frameworks verwenden die Generierung ebenfalls, um typsichere Datenbankabfragen oder Client-Code zu erstellen.

Der entscheidende Vorteil gegenüber Reflection besteht darin, dass generierter Code vom Compiler geprüft wird. Fehler werden zur Build-Zeit und nicht zur Laufzeit erkannt. Der Nachteil ist ein zusätzlicher Build-Schritt sowie generierte Dateien, die du in deinem Repository verwalten musst. Für OOP-Muster, die sich wiederholende Implementierungen für viele Typen erfordern, bietet die Codegenerierung oft das beste Gleichgewicht zwischen Sicherheit und Komfort.

challenge icon

Aufgabe

Einfach

Erstellen wir einen Codegenerator, der String()-Methoden für enumartige Typen erzeugt! Obwohl wir in dieser Umgebung kein tatsächliches go generate ausführen können, simulierst du, was Codegenerierungstools tun: Go-Quellcode programmgesteuert auf Grundlage von Typdefinitionen erzeugen.

Du organisierst deinen Code über zwei Dateien:

  • generator.go: Erstelle deine Logik zur Codegenerierung.

    Erstelle eine EnumType-Struktur, die eine enumartige Typdefinition mit einem Name (String) und Values (Slice von Strings, die die Namen der Konstanten darstellen) repräsentiert.

    Erstelle eine Funktion GenerateStringMethod(e EnumType) string, die Go-Quellcode für eine String()-Methode erzeugt. Der generierte Code sollte diesem Muster folgen:

    func (e TypeName) String() string {
        switch e {
        case Value1:
            return "Value1"
        case Value2:
            return "Value2"
        default:
            return "Unknown"
        }
    }

    Erstelle außerdem eine Funktion GenerateConstants(e EnumType) string, die mithilfe von iota die Konstantendeklarationen erzeugt:

    const (
        Value1 TypeName = iota
        Value2
        Value3
    )

    Erstelle schließlich GenerateFullCode(e EnumType) string, das beide Ausgaben mit einem Kopfkommentar kombiniert, der angibt, dass es sich um generierten Code handelt:

    // Code generated by enum generator. DO NOT EDIT.
    
    [constants]
    
    [string method]
  • main.go: Verwende deinen Generator, um Code für verschiedene enumartige Typen zu erzeugen.

    Lies den Namen des enumartigen Typs ein, danach eine Anzahl von Werten und anschließend jeden Wertnamen. Übergib diese Informationen an deinen Generator und gib den vollständig generierten Code aus.

Die folgenden Eingaben werden bereitgestellt:

  • Zeile 1: Name des enumartigen Typs
  • Zeile 2: Anzahl der Werte
  • Folgende Zeilen: Jeder Wertname

Zum Beispiel bei:

Status
3
Pending
Active
Completed

sollte deine Ausgabe folgendermaßen aussehen:

// 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"
    }
}

Und bei:

Priority
2
Low
High

sollte deine Ausgabe folgendermaßen aussehen:

// 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"
    }
}

Diese Übung veranschaulicht das grundlegende Konzept hinter Tools wie stringer: Wiederholenden, aber typsicheren Code zur Kompilierzeit zu erzeugen, anstatt zur Laufzeit Reflection zu verwenden. Dein Generator erzeugt gültigen Go-Code, der in einer Datei gespeichert und zusammen mit dem restlichen Projekt kompiliert werden könnte.

Probier es selbst

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	reader := bufio.NewReader(os.Stdin)

	// Enum-Typnamen lesen
	typeName, _ := reader.ReadString('\n')
	typeName = strings.TrimSpace(typeName)

	// Anzahl der Werte lesen
	countStr, _ := reader.ReadString('\n')
	count, _ := strconv.Atoi(strings.TrimSpace(countStr))

	// Jeden Wertnamen lesen
	values := make([]string, count)
	for i := 0; i < count; i++ {
		value, _ := reader.ReadString('\n')
		values[i] = strings.TrimSpace(value)
	}

	// TODO: Erstelle ein EnumType mit den gelesenen Daten

	// TODO: Generiere und drucke den vollständigen Code mit GenerateFullCode
}
quiz iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Objektorientierte Programmierung

Übe selbstständig: Online-Go-Compiler