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.
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
EnumTypeque represente una definición de tipo similar a un enum con unName(string) yValues(slice de strings que representan los nombres de las constantes).Crea una función
GenerateStringMethod(e EnumType) stringque produzca código fuente de Go para un métodoString(). 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) stringque produzca las declaraciones de constantes usandoiota:const ( Value1 TypeName = iota Value2 Value3 )Finalmente, crea
GenerateFullCode(e EnumType) stringque 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
CompletedTu 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
HighTu 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étodosString()para tipos enumerados (enum)mockgen- crea simulacros de prueba (mocks) a partir de interfacesprotoc- 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
}
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
1Fundamentos de POO en Go
Archivos externosEspacio de trabajo y módulos de GoPaquetes e importacionesNombres exportados vs. no exportadosIntroducción a la POO en GoStructs como clasesDefinición de métodos en structsReceptores de puntero vs. de valorInicialización de structsFunciones constructorasResumen - Calculadora simple4Interfaces
Introducción a las interfacesImplementación implícitaLa interfaz como contratoInterfaz vacía (any)Type AssertionType SwitchComposición de interfacesInterfaces Stringer y ErrorResumen: Calculadora de figuras7Encapsulamiento
Campos exportados vs. no exportadosEncapsulamiento a nivel de paqueteMétodos Getter y SetterOcultamiento de información en GoResumen - Registros de estudiantes10Genéricos (Go 1.18+)
Introducción a los GenéricosParámetros de tipoRestricciones de tipoStructs genéricosSolución alternativa para métodos genéricosResumen - Colección genérica13Patrones de diseño - Parte 1
Introducción a los patrones de diseñoPatrón SingletonPatrón FactoryPatrón Abstract FactoryPatrón ObserverPatrón Strategy2Inmersión profunda en Tipos y Structs
Tipos básicos y compuestosDefiniciones de tipos personalizadosEtiquetas de StructsStructs anónimosStructs anidadosValores cero y por defectoRepaso - Libreta de contactos5Composición sobre herencia
Por qué Go no tiene herenciaConceptos básicos de incrustación de structsPromoción de métodosIncrustación de múltiples structsIncrustación vs AgregaciónShadowing de métodos incrustadosResumen - Jerarquía de empleados8Manejo de Errores y OOP
La interfaz errorTipos de errores personalizadosEnvoltura de errores (fmt.Errorf)Errores centinelaerrors.Is() y errors.As()Panic, Defer y RecoverResumen - Analizador de archivos3Punteros y Memoria
Fundamentos de punteros en GoPunteros a StructsPaso por valor vs referenciaLa función new()Garbage Collection en GoResumen - Constructor de Linked List6Polimorfismo en Go
Polimorfismo mediante interfacesDuck Typing en GoReglas de satisfacción de interfacesColecciones polimórficasInyección de dependenciasResumen - Procesador de pagos9Concurrencia y POO
Fundamentos de GoroutinesChannels y comunicaciónChannels con buffer vs. sin bufferSentencia Selectsync.Mutex y sync.RWMutexsync.WaitGroupDiseño de estructuras Thread-SafeResumen - Worker Pool12Conceptos avanzados de POO
Patrón Functional OptionsPatrón Builder en GoEncadenamiento de métodosAlias de tipos vs. definicionesFundamentos de ReflectionDescripción general de generación de código