Menu
Coddy logo textTech

Przegląd generowania kodu

Część sekcji Programowanie obiektowe ścieżki GO w Coddy. Lekcja 84 z 107.

Generowanie kodu jest alternatywą dla refleksji, która tworzy kod źródłowy Go podczas kompilacji zamiast sprawdzać typy w czasie wykonywania. To podejście zapewnia bezpieczeństwo typów i lepszą wydajność, a jednocześnie ogranicza powtarzalny kod szablonowy.

Go udostępnia polecenie go generate, które automatyzuje generowanie kodu. Umieszczasz w pliku źródłowym specjalną dyrektywę w komentarzu, a uruchomienie go generate wykonuje wskazane narzędzie:

//go:generate stringer -type=Status

type Status int

const (
    Pending Status = iota
    Active
    Completed
)

Uruchomienie go generate ./... wywołuje narzędzie stringer, które tworzy nowy plik zawierający metodę String() dla typu Status. Wygenerowany kod to zwykły kod Go, który jest kompilowany wraz z programem.

Popularne narzędzia do generowania kodu w ekosystemie Go to między innymi stringer do tworzenia metod zwracających ciągi znaków dla typów wyliczeniowych, mockgen do tworzenia atrap testowych na podstawie interfejsów oraz protoc do generowania kodu z definicji Protocol Buffer. Wiele ORM-ów i frameworków API również korzysta z generowania kodu, aby tworzyć bezpieczne pod względem typów zapytania do baz danych lub kod klienta.

Kluczową zaletą w porównaniu z refleksją jest to, że wygenerowany kod jest sprawdzany przez kompilator. Błędy są wykrywane podczas kompilacji, a nie w czasie działania programu. Ceną za to jest dodatkowy etap kompilacji i pliki generowane, którymi trzeba zarządzać w repozytorium. W przypadku wzorców OOP wymagających powtarzalnych implementacji dla wielu typów generowanie kodu często zapewnia najlepszy kompromis między bezpieczeństwem a wygodą.

challenge icon

Wyzwanie

Łatwy

Zbudujmy generator kodu, który tworzy metody String() dla typów przypominających wyliczenia! Chociaż w tym środowisku nie możemy uruchomić rzeczywistego go generate, zasymulujesz działanie narzędzi do generowania kodu: programowe tworzenie kodu źródłowego Go na podstawie definicji typów.

Rozmieścisz kod w dwóch plikach:

  • generator.go: Utwórz logikę generowania kodu.

    Zbuduj strukturę EnumType, która reprezentuje definicję typu przypominającego wyliczenie, z polem Name (string) i polem Values (slice ciągów znaków reprezentujących nazwy stałych).

    Utwórz funkcję GenerateStringMethod(e EnumType) string, która generuje kod źródłowy Go dla metody String(). Wygenerowany kod powinien mieć następującą postać:

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

    Utwórz również funkcję GenerateConstants(e EnumType) string, która generuje deklaracje stałych przy użyciu iota:

    const (
        Value1 TypeName = iota
        Value2
        Value3
    )

    Na koniec utwórz GenerateFullCode(e EnumType) string, która łączy oba wyniki i dodaje komentarz nagłówkowy informujący, że jest to wygenerowany kod:

    // Code generated by enum generator. DO NOT EDIT.
    
    [constants]
    
    [string method]
  • main.go: Użyj generatora, aby wygenerować kod dla różnych typów przypominających wyliczenia.

    Wczytaj nazwę typu, a następnie liczbę wartości i kolejne nazwy wartości. Przekaż te informacje do generatora i wypisz kompletny wygenerowany kod.

Zostaną podane następujące dane wejściowe:

  • Wiersz 1: Nazwa typu przypominającego wyliczenie
  • Wiersz 2: Liczba wartości
  • Kolejne wiersze: Nazwa każdej wartości

Na przykład dla danych:

Status
3
Pending
Active
Completed

Wynik powinien być następujący:

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

A dla danych:

Priority
2
Low
High

Wynik powinien być następujący:

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

To ćwiczenie pokazuje podstawową koncepcję stojącą za narzędziami takimi jak stringer: generowanie powtarzalnego, ale bezpiecznego typowo kodu podczas kompilacji zamiast używania refleksji w czasie wykonywania. Twój generator tworzy poprawny kod Go, który można zapisać do pliku i skompilować wraz z resztą projektu.

Spróbuj swoich sił

package main

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

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

	// Wczytaj nazwę typu wyliczeniowego
	typeName, _ := reader.ReadString('\n')
	typeName = strings.TrimSpace(typeName)

	// Wczytaj liczbę wartości
	countStr, _ := reader.ReadString('\n')
	count, _ := strconv.Atoi(strings.TrimSpace(countStr))

	// Wczytaj nazwę każdej wartości
	values := make([]string, count)
	for i := 0; i < count; i++ {
		value, _ := reader.ReadString('\n')
		values[i] = strings.TrimSpace(value)
	}

	// TODO: Utwórz EnumType na podstawie wczytanych danych

	// TODO: Wygeneruj i wypisz pełny kod za pomocą GenerateFullCode
}
quiz iconSprawdź się

Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.

Wszystkie lekcje w sekcji Programowanie obiektowe

Poćwicz samodzielnie: Kompilator Go online