Ü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.
Aufgabe
EinfachLassen 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 einemName(String) undValues(Slice von Strings, die die Konstantennamen darstellen) repräsentiert.Erstellen Sie eine Funktion
GenerateStringMethod(e EnumType) string, die Go-Quellcode für eineString()-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 voniotaerzeugt: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
CompletedSollte 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
HighSollte 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- generiertString()-Methoden für Enum-Typenmockgen- erstellt Test-Mocks aus Interfacesprotoc- 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
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Grundlagen der Go OOP
Externe DateienGo Workspace & ModulePackages & ImportsExportierte vs. nicht-exportierte NamenEinführung in OOP in GoStructs als KlassenMethoden für Structs definierenPointer- vs. Value-ReceiverStruct-InitialisierungKonstruktor-FunktionenZusammenfassung – Einfacher Taschenrechner4Interfaces
Einführung in InterfacesImplizite ImplementierungInterface als VertragLeeres Interface (any)Type AssertionType SwitchInterface-KompositionStringer & Error InterfacesRückblick - Formen-Rechner7Kapselung
Exportierte vs. nicht exportierte FelderKapselung auf Paket-EbeneGetter- & Setter-MethodenInformation Hiding in GoZusammenfassung - Studierendendatensätze10Generics (Go 1.18+)
Einführung in GenericsTypparameterType ConstraintsGenerische StructsWorkaround für generische MethodenZusammenfassung – Generische Collection2Vertiefung: Typen & Structs
Basis- & zusammengesetzte TypenBenutzerdefinierte TypdefinitionenStruct TagsAnonyme StructsVerschachtelte StructsZero Values & StandardwerteZusammenfassung - Kontaktbuch5Komposition statt Vererbung
Warum Go keine Vererbung hatGrundlagen des Struct EmbeddingMethod PromotionEinbetten mehrerer StructsEmbedding vs. AggregationShadowing eingebetteter MethodenZusammenfassung – Mitarbeiter-Hierarchie8Fehlerbehandlung & OOP
Das error-InterfaceEigene FehlertypenError Wrapping (fmt.Errorf)Sentinel-Fehlererrors.Is() und errors.As()Panic, Defer und RecoverZusammenfassung – Datei-Parser3Zeiger & Speicher
Zeiger-Grundlagen in GoZeiger auf StructsWertübergabe vs. ReferenzübergabeDie new() FunktionGarbage Collection in GoWiederholung – Linked List Builder6Polymorphismus in Go
Polymorphismus über InterfacesDuck Typing in GoInterface-ErfüllungsregelnPolymorphe CollectionsDependency InjectionZusammenfassung – Payment Processor9Nebenläufigkeit & OOP
Goroutines GrundlagenChannels & KommunikationGepufferte vs. ungepufferte ChannelsSelect-Anweisungsync.Mutex & sync.RWMutexsync.WaitGroupThread-sicheres Struct-DesignZusammenfassung - Worker Pool12Fortgeschrittene OOP-Konzepte
Functional Options PatternBuilder-Pattern in GoMethodenverkettungTyp-Aliase vs. DefinitionenReflection-GrundlagenÜberblick zur Code-Generierung