Menu
Coddy logo textTech

Überblick zur Code-Generierung

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 inspizieren. Dieser Ansatz bietet Typsicherheit und eine bessere Performance, während er gleichzeitig repetitiven Boilerplate-Code reduziert.

Go bietet den Befehl go generate an, um die Codegenerierung zu automatisieren. Sie platzieren eine spezielle Kommentaranweisung in Ihrer Quelldatei, und das Ausführen von go generate führt das angegebene Tool aus:

//go:generate stringer -type=Status

type Status int

const (
    Pending Status = iota
    Active
    Completed
)

Das Ausführen von go generate ./... ruft das stringer-Tool auf, welches eine neue Datei erstellt, die eine String()-Methode für den Status-Typ enthält. Der generierte Code ist reguläres Go, das mit Ihrem Programm kompiliert wird.

Gängige Tools zur Codegenerierung im Go-Ökosystem umfassen stringer für Enum-String-Methoden, mockgen zum Erstellen von Test-Mocks aus Interfaces und protoc für die Generierung von Code aus Protocol-Buffer-Definitionen. Viele ORMs und API-Frameworks verwenden ebenfalls Generierung, um typsichere Datenbankabfragen oder Client-Code zu erstellen.

Der entscheidende Vorteil gegenüber Reflection ist, dass generierter Code vom Compiler geprüft wird. Fehler werden zur Build-Zeit abgefangen, nicht zur Laufzeit. Der Kompromiss besteht in einem zusätzlichen Build-Schritt und generierten Dateien, die in Ihrem Repository verwaltet werden müssen. Für OOP-Muster, die repetitive Implementierungen über viele Typen hinweg erfordern, bietet die Codegenerierung oft das beste Gleichgewicht zwischen Sicherheit und Komfort.

challenge icon

Aufgabe

Einfach

Lassen Sie uns einen Codegenerator bauen, der String()-Methoden für Enum-ähnliche Typen erstellt! Obwohl wir in dieser Umgebung kein echtes go generate ausführen können, werden Sie simulieren, was Codegenerierungswerkzeuge tun – Go-Quellcode programmatisch basierend auf Typdefinitionen erzeugen.

Sie werden Ihren Code auf zwei Dateien verteilen:

  • generator.go: Erstellen Sie Ihre Codegenerierungslogik.

    Erstellen Sie ein EnumType-Struct, das eine Enum-ähnliche Typdefinition mit einem Name (String) und Values (Slice von Strings, die die Konstantennamen darstellen) repräsentiert.

    Erstellen Sie 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"
        }
    }

    Erstellen Sie außerdem eine Funktion GenerateConstants(e EnumType) string, welche die Konstantendeklarationen unter Verwendung von iota erzeugt:

    const (
        Value1 TypeName = iota
        Value2
        Value3
    )

    Erstellen Sie schließlich GenerateFullCode(e EnumType) string, das beide Ausgaben mit einem Header-Kommentar kombiniert, der darauf hinweist, dass dies generierter Code ist:

    // Code generated by enum generator. DO NOT EDIT.
    
    [constants]
    
    [string method]
  • main.go: Verwenden Sie Ihren Generator, um Code für verschiedene Enum-Typen zu erzeugen.

    Lesen Sie den Namen des Enum-Typs ein, lesen Sie dann die Anzahl der Werte, gefolgt von jedem Wertnamen. Übergeben Sie diese Informationen an Ihren Generator und geben Sie den vollständigen generierten Code aus.

Die folgenden Eingaben werden bereitgestellt:

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

Zum Beispiel, gegeben:

Status
3
Pending
Active
Completed

Sollte Ihre Ausgabe wie folgt 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 gegeben:

Priority
2
Low
High

Sollte Ihre Ausgabe wie folgt 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 demonstriert das Kernkonzept hinter Werkzeugen wie stringer – das Erzeugen von repetitivem, aber typsicherem Code zur Kompilierzeit, anstatt Reflexion zur Laufzeit zu verwenden. Ihr Generator erzeugt gültigen Go-Code, der in einer Datei gespeichert und mit dem Rest eines Projekts kompiliert werden könnte.

Spickzettel

Code-Generierung erstellt Go-Quellcode zur Kompilierzeit, anstatt Typen zur Laufzeit zu untersuchen, was Typsicherheit und bessere Performance bietet und gleichzeitig Boilerplate-Code reduziert.

Der Befehl go generate automatisiert die Code-Generierung mithilfe spezieller Kommentar-Direktiven:

//go:generate stringer -type=Status

type Status int

const (
    Pending Status = iota
    Active
    Completed
)

Das Ausführen von go generate ./... ruft das angegebene Tool auf (wie stringer), welches eine neue Datei mit generiertem Code erstellt, die zusammen mit Ihrem Programm kompiliert wird.

Zu den gängigen Code-Generierungstools gehören:

  • stringer - generiert String()-Methoden für Enum-Typen
  • mockgen - erstellt Test-Mocks aus Interfaces
  • protoc - generiert Code aus Protocol-Buffer-Definitionen

Wichtige Vorteile gegenüber Reflection:

  • Generierter Code wird vom Compiler geprüft
  • Fehler werden zur Build-Zeit abgefangen, nicht zur Laufzeit
  • Bessere Performance

Zu den Nachteilen gehören ein zusätzlicher Build-Schritt und generierte Dateien, die in Ihrem Repository verwaltet werden müssen.

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: Ein EnumType mit den gelesenen Daten erstellen

	// TODO: Den vollständigen Code mit GenerateFullCode generieren und ausgeben
}
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