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.
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
EnumTypeque represente una definición de tipo similar a una enumeración, con unName(cadena) yValues(segmento de cadenas que representa 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" } }Crea también una función
GenerateConstants(e EnumType) stringque produzca las declaraciones de constantes medianteiota: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
CompletedTu 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
HighTu 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
}
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ódigoPractica por tu cuenta: Compilador de Go online