Menu
Coddy logo textTech

Descripción general de generación de código

Parte de la sección Programación Orientada a Objetos del Journey de GO de Coddy. Lección 84 de 107.

La generación de código es una alternativa a la reflexión que crea código fuente de Go en tiempo de compilación en lugar de inspeccionar tipos en tiempo de ejecución. Este enfoque te proporciona seguridad de tipos y un mejor rendimiento, al tiempo que reduce el código repetitivo y rutinario.

Go proporciona el comando go generate para automatizar la generación de código. Colocas una directiva de comentario especial en tu archivo fuente y, al ejecutar go generate, se ejecuta la herramienta especificada:

//go:generate stringer -type=Status

type Status int

const (
    Pending Status = iota
    Active
    Completed
)

Ejecutar go generate ./... invoca la herramienta stringer, que crea un archivo nuevo que contiene un método String() para el tipo Status. El código generado es código Go normal que se compila con tu programa.

Las herramientas comunes de generación de código en el ecosistema de Go incluyen stringer para los métodos de cadena de las enumeraciones, mockgen para crear simulaciones de prueba a partir de interfaces y protoc para generar código a partir de definiciones de Protocol Buffer. Muchos ORM y marcos de trabajo de API también utilizan la generación para crear consultas de base de datos con seguridad de tipos o código de cliente.

La principal ventaja frente a la reflexión es que el código generado es comprobado por el compilador. Los errores se detectan durante la compilación, no en tiempo de ejecución. La contrapartida es un paso de compilación adicional y archivos generados que debes gestionar en tu repositorio. Para los patrones de POO que requieren implementaciones repetitivas en muchos tipos, la generación de código suele ofrecer el mejor equilibrio entre seguridad y comodidad.

challenge icon

Desafío

Fácil

¡Construyamos un generador de código que cree métodos String() para tipos similares a enumeraciones! Aunque no podemos ejecutar un go generate real en este entorno, simularás lo que hacen las herramientas de generación de código: producir código fuente de Go mediante programación a partir de definiciones de tipos.

Organizarás tu código en dos archivos:

  • generator.go: Crea la lógica de generación de código.

    Construye una estructura EnumType que represente una definición de tipo similar a una enumeración, con un Name (cadena) y Values (segmento de cadenas que representa los nombres de las constantes).

    Crea una función GenerateStringMethod(e EnumType) string que produzca código fuente de Go para un método String(). El código generado debe seguir este patrón:

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

    Crea también una función GenerateConstants(e EnumType) string que produzca las declaraciones de constantes mediante iota:

    const (
        Value1 TypeName = iota
        Value2
        Value3
    )

    Por último, crea GenerateFullCode(e EnumType) string, que combine ambas salidas con un comentario de encabezado que indique que se trata de código generado:

    // Code generated by enum generator. DO NOT EDIT.
    
    [constants]
    
    [string method]
  • main.go: Usa tu generador para producir código para distintos tipos de enumeración.

    Lee el nombre del tipo de enumeración y, después, lee una cantidad de valores seguida de cada nombre de valor. Pasa esta información a tu generador e imprime el código completo generado.

Se proporcionarán las siguientes entradas:

  • Línea 1: Nombre del tipo de enumeración
  • Línea 2: Número de valores
  • Líneas siguientes: Cada nombre de valor

Por ejemplo, dada la siguiente entrada:

Status
3
Pending
Active
Completed

Tu salida debe ser:

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

Y dada la siguiente entrada:

Priority
2
Low
High

Tu salida debe ser:

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

Este ejercicio demuestra el concepto fundamental en el que se basan herramientas como stringer: generar código repetitivo pero seguro con respecto a los tipos durante la compilación, en lugar de usar reflexión durante la ejecución. Tu generador produce código Go válido que podría guardarse en un archivo y compilarse junto con el resto de un proyecto.

Pruébalo tú mismo

package main

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

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

	// Leer el nombre del tipo enum
	typeName, _ := reader.ReadString('\n')
	typeName = strings.TrimSpace(typeName)

	// Leer el número de valores
	countStr, _ := reader.ReadString('\n')
	count, _ := strconv.Atoi(strings.TrimSpace(countStr))

	// Leer el nombre de cada valor
	values := make([]string, count)
	for i := 0; i < count; i++ {
		value, _ := reader.ReadString('\n')
		values[i] = strings.TrimSpace(value)
	}

	// TODO: Crear un EnumType con los datos leídos

	// TODO: Generar e imprimir el código completo usando GenerateFullCode
}
quiz iconPonte a prueba

Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.

Todas las lecciones de Programación Orientada a Objetos

Practica por tu cuenta: Compilador de Go online