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 brinda seguridad de tipos y un mejor rendimiento, al mismo tiempo que reduce el código repetitivo (boilerplate).

Go proporciona el comando go generate para automatizar la generación de código. Se coloca una directiva de comentario especial en el 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
)

Al ejecutar go generate ./... se invoca la herramienta stringer, la cual crea un nuevo archivo que contiene un método String() para el tipo Status. El código generado es Go estándar que se compila con su programa.

Las herramientas comunes de generación de código en el ecosistema de Go incluyen stringer para métodos de cadena de enumeraciones, mockgen para crear simulacros de prueba a partir de interfaces, y protoc para generar código a partir de definiciones de Protocol Buffer. Muchos ORMs y frameworks de API también utilizan la generación para crear consultas de base de datos seguras en cuanto a tipos o código de cliente.

La ventaja clave sobre la reflexión es que el código generado es verificado por el compilador. Los errores se detectan en tiempo de compilación, no en tiempo de ejecución. La contrapartida es un paso de compilación adicional y archivos generados que 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 conveniencia.

challenge icon

Desafío

Fácil

¡Vamos a construir un generador de código que crea métodos String() para tipos similares a enums! Aunque no podemos ejecutar 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 programáticamente basado en definiciones de tipos.

Organizarás tu código en dos archivos:

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

    Construye una estructura EnumType que represente una definición de tipo similar a un enum con un Name (string) y Values (slice de strings que representan 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"
        }
    }

    También crea una función GenerateConstants(e EnumType) string que produzca las declaraciones de constantes usando iota:

    const (
        Value1 TypeName = iota
        Value2
        Value3
    )

    Finalmente, crea GenerateFullCode(e EnumType) string que combine ambas salidas con un comentario de encabezado indicando que este es código generado:

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

    Lee el nombre del tipo de enum, luego lee un recuento de valores seguido de cada nombre de valor. Pasa esta información a tu generador e imprime el código generado completo.

Se proporcionarán las siguientes entradas:

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

Por ejemplo, dado:

Status
3
Pending
Active
Completed

Tu salida debería 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 dado:

Priority
2
Low
High

Tu salida debería 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 central detrás de herramientas como stringer: generar código repetitivo pero seguro para los tipos en tiempo de compilación en lugar de usar reflexión en tiempo de ejecución. Tu generador produce código Go válido que podría guardarse en un archivo y compilarse con el resto de un proyecto.

Hoja de referencia

La generación de código crea código fuente de Go en tiempo de compilación en lugar de inspeccionar tipos en tiempo de ejecución, proporcionando seguridad de tipos y un mejor rendimiento al tiempo que reduce el código repetitivo (boilerplate).

El comando go generate automatiza la generación de código utilizando directivas de comentarios especiales:

//go:generate stringer -type=Status

type Status int

const (
    Pending Status = iota
    Active
    Completed
)

Ejecutar go generate ./... invoca la herramienta especificada (como stringer), que crea un nuevo archivo que contiene el código generado que se compila con su programa.

Las herramientas comunes de generación de código incluyen:

  • stringer - genera métodos String() para tipos enumerados (enum)
  • mockgen - crea simulacros de prueba (mocks) a partir de interfaces
  • protoc - genera código a partir de definiciones de Protocol Buffer

Ventajas clave sobre la reflexión:

  • El código generado es verificado por el compilador
  • Los errores se detectan en tiempo de compilación, no en tiempo de ejecución
  • Mejor rendimiento

Las desventajas incluyen un paso de compilación adicional y archivos generados que deben gestionarse en su repositorio.

Pruébalo tú mismo

package main

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

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

	// Leer el nombre del tipo de enumeración
	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