Menu
Coddy logo textTech

Panoramica sulla generazione del codice

Fa parte della sezione Programmazione orientata agli oggetti del percorso GO di Coddy. Lezione 84 di 107.

La generazione del codice è un’alternativa alla reflection che crea codice sorgente Go al momento della compilazione invece di esaminare i tipi durante l’esecuzione. Questo approccio ti offre sicurezza dei tipi e prestazioni migliori, riducendo comunque il codice boilerplate ripetitivo.

Go fornisce il comando go generate per automatizzare la generazione del codice. Inserisci una direttiva di commento speciale nel file sorgente e, eseguendo go generate, verrà eseguito lo strumento specificato:

//go:generate stringer -type=Status

type Status int

const (
    Pending Status = iota
    Active
    Completed
)

L'esecuzione di go generate ./... richiama lo strumento stringer, che crea un nuovo file contenente un metodo String() per il tipo Status. Il codice generato è normale codice Go, che viene compilato insieme al tuo programma.

Gli strumenti comuni per la generazione di codice nell'ecosistema Go includono stringer per i metodi stringa degli enum, mockgen per creare mock di test dalle interfacce e protoc per generare codice dalle definizioni di Protocol Buffer. Anche molti ORM e framework API usano la generazione per creare query al database o codice client con sicurezza dei tipi.

Il vantaggio principale rispetto alla reflection è che il codice generato viene verificato dal compilatore. Gli errori vengono rilevati durante la compilazione, non durante l'esecuzione. Il compromesso è un passaggio di compilazione aggiuntivo e la gestione dei file generati nel repository. Per i pattern OOP che richiedono implementazioni ripetitive in molti tipi, la generazione di codice offre spesso il miglior equilibrio tra sicurezza e praticità.

challenge icon

Sfida

Facile

Costruiamo un generatore di codice che crea metodi String() per tipi simili a enum! Anche se in questo ambiente non possiamo eseguire un vero go generate, simulerai ciò che fanno gli strumenti di generazione del codice: produrre codice sorgente Go programmaticamente in base alle definizioni dei tipi.

Organizzerai il tuo codice in due file:

  • generator.go: crea la logica di generazione del codice.

    Crea una struct EnumType che rappresenta la definizione di un tipo simile a enum con un Name (stringa) e Values (slice di stringhe che rappresentano i nomi delle costanti).

    Crea una funzione GenerateStringMethod(e EnumType) string che produce codice sorgente Go per un metodo String(). Il codice generato dovrebbe seguire questo schema:

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

    Crea anche una funzione GenerateConstants(e EnumType) string che produce le dichiarazioni delle costanti usando iota:

    const (
        Value1 TypeName = iota
        Value2
        Value3
    )

    Infine, crea GenerateFullCode(e EnumType) string per combinare entrambi gli output con un commento di intestazione che indica che si tratta di codice generato:

    // Code generated by enum generator. DO NOT EDIT.
    
    [constants]
    
    [string method]
  • main.go: usa il tuo generatore per produrre codice per diversi tipi enum.

    Leggi il nome del tipo enum, quindi leggi un numero di valori seguito dal nome di ciascun valore. Passa queste informazioni al tuo generatore e stampa il codice generato completo.

Verranno forniti i seguenti input:

  • Riga 1: nome del tipo enum
  • Riga 2: numero di valori
  • Righe successive: nome di ciascun valore

Per esempio, dato:

Status
3
Pending
Active
Completed

Il tuo output dovrebbe essere:

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

E dato:

Priority
2
Low
High

Il tuo output dovrebbe essere:

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

Questo esercizio illustra il concetto fondamentale alla base di strumenti come stringer: generare codice ripetitivo ma sicuro rispetto ai tipi in fase di compilazione, anziché usare la reflection in fase di esecuzione. Il tuo generatore produce codice Go valido che potrebbe essere salvato in un file e compilato insieme al resto di un progetto.

Provalo tu

package main

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

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

	// Leggi il nome del tipo enum
	typeName, _ := reader.ReadString('\n')
	typeName = strings.TrimSpace(typeName)

	// Leggi il numero di valori
	countStr, _ := reader.ReadString('\n')
	count, _ := strconv.Atoi(strings.TrimSpace(countStr))

	// Leggi il nome di ogni valore
	values := make([]string, count)
	for i := 0; i < count; i++ {
		value, _ := reader.ReadString('\n')
		values[i] = strings.TrimSpace(value)
	}

	// TODO: Crea un EnumType con i dati letti

	// TODO: Genera e stampa il codice completo usando GenerateFullCode
}
quiz iconMettiti alla prova

Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.

Tutte le lezioni di Programmazione orientata agli oggetti

Esercitati da solo: Compilatore Go online