Visão Geral da Geração de Código
Parte da seção Programação Orientada a Objetos do Journey de GO da Coddy. Lição 84 de 107.
Geração de código é uma alternativa à reflexão que cria código-fonte de Go em tempo de compilação, em vez de inspecionar tipos em tempo de execução. Essa abordagem oferece segurança de tipos e melhor desempenho, ao mesmo tempo que reduz o código boilerplate repetitivo.
Go fornece o comando go generate para automatizar a geração de código. Você coloca uma diretiva de comentário especial no seu arquivo-fonte, e executar go generate executa a ferramenta especificada:
//go:generate stringer -type=Status
type Status int
const (
Pending Status = iota
Active
Completed
)Executar go generate ./... invoca a ferramenta stringer, que cria um novo arquivo contendo um método String() para o tipo Status. O código gerado é Go comum, compilado junto com o seu programa.
Ferramentas comuns de geração de código no ecossistema Go incluem stringer para métodos de string de enumerações, mockgen para criar mocks de teste a partir de interfaces e protoc para gerar código a partir de definições do Protocol Buffer. Muitos ORMs e frameworks de API também usam geração para criar consultas de banco de dados com segurança de tipos ou código de cliente.
A principal vantagem em relação à reflexão é que o código gerado é verificado pelo compilador. Os erros são detectados no momento da compilação, não em tempo de execução. A desvantagem é uma etapa adicional de compilação e arquivos gerados para gerenciar em seu repositório. Para padrões de OOP que exigem implementações repetitivas em muitos tipos, a geração de código geralmente oferece o melhor equilíbrio entre segurança e conveniência.
Desafio
FácilVamos criar um gerador de código que cria métodos String() para tipos semelhantes a enumerações! Embora não possamos executar um go generate real neste ambiente, você simulará o que as ferramentas de geração de código fazem: produzir código-fonte Go programaticamente com base em definições de tipos.
Você organizará seu código em dois arquivos:
generator.go: Crie sua lógica de geração de código.Crie uma struct
EnumTypeque represente uma definição de tipo semelhante a uma enumeração, com umName(string) eValues(slice de strings que representa os nomes das constantes).Crie uma função
GenerateStringMethod(e EnumType) stringque produza código-fonte Go para um métodoString(). O código gerado deve seguir este padrão:func (e TypeName) String() string { switch e { case Value1: return "Value1" case Value2: return "Value2" default: return "Unknown" } }Crie também uma função
GenerateConstants(e EnumType) stringque produza as declarações de constantes usandoiota:const ( Value1 TypeName = iota Value2 Value3 )Por fim, crie
GenerateFullCode(e EnumType) string, que combine ambas as saídas com um comentário de cabeçalho indicando que este é um código gerado:// Code generated by enum generator. DO NOT EDIT. [constants] [string method]main.go: Use seu gerador para produzir código para diferentes tipos semelhantes a enumerações.Leia o nome do tipo semelhante a uma enumeração, depois leia uma quantidade de valores seguida pelo nome de cada valor. Passe essas informações ao seu gerador e imprima o código completo gerado.
As seguintes entradas serão fornecidas:
- Linha 1: Nome do tipo semelhante a uma enumeração
- Linha 2: Número de valores
- Linhas seguintes: Nome de cada valor
Por exemplo, dadas as entradas:
Status
3
Pending
Active
CompletedSua saída deve 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"
}
}E, dadas as entradas:
Priority
2
Low
HighSua saída deve 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 exercício demonstra o conceito central por trás de ferramentas como stringer: gerar código repetitivo, mas seguro quanto aos tipos, no momento da compilação, em vez de usar reflexão durante a execução. Seu gerador produz código Go válido que poderia ser salvo em um arquivo e compilado com o restante de um projeto.
Experimente você mesmo
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Ler o nome do tipo enum
typeName, _ := reader.ReadString('\n')
typeName = strings.TrimSpace(typeName)
// Ler o número de valores
countStr, _ := reader.ReadString('\n')
count, _ := strconv.Atoi(strings.TrimSpace(countStr))
// Ler o nome de cada valor
values := make([]string, count)
for i := 0; i < count; i++ {
value, _ := reader.ReadString('\n')
values[i] = strings.TrimSpace(value)
}
// TODO: Criar um EnumType com os dados lidos
// TODO: Gerar e imprimir o código completo usando GenerateFullCode
}
Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.
Todas as lições de Programação Orientada a Objetos
1Fundamentos de OOP em Go
Arquivos ExternosWorkspace e Módulos do GoPacotes e ImportsNomes Exportados vs. Não ExportadosIntrodução à OOP em GoStructs como ClassesDefinindo Métodos em StructsReceptores de Ponteiro vs. de ValorInicialização de StructsFunções ConstrutorasRecapitulação — Calculadora Simples4Interfaces
Introdução às InterfacesImplementação ImplícitaInterface como ContratoInterface Vazia (any)Asserção de TipoSwitch de TipoComposição de InterfacesInterfaces Stringer e ErrorRecapitulação - Calculadora de Formas7Encapsulamento
Campos Exportados vs. Não ExportadosEncapsulamento no Nível de PacoteMétodos Getters e SettersOcultação de Informações em GoRecapitulação - Registros de Alunos10Genéricos (Go 1.18+)
Introdução aos genéricosParâmetros de tipoRestrições de tipoStructs genéricasAlternativa para métodos genéricosRecapitulação - Coleção genérica13Padrões de Projeto — Parte 1
Introdução aos Padrões de ProjetoPadrão SingletonPadrão FactoryPadrão Abstract FactoryPadrão ObserverPadrão Strategy2Tipos e Structs em Profundidade
Tipos Básicos e CompostosDefinições de Tipos PersonalizadosTags de StructStructs AnônimasStructs AninhadasValores Zero e PadrõesRecapitulação - Livro de Contatos5Composição em vez de herança
Por que Go não tem herançaNoções básicas de incorporação de structsPromoção de métodosIncorporação de várias structsIncorporação vs agregaçãoOcultação de métodos incorporadosRecapitulação - Hierarquia de funcionários8Tratamento de Erros e OOP
A interface errorTipos de Erro PersonalizadosEncapsulamento de Erros (fmt.Errorf)Erros Sentinelaerrors.Is() e errors.As()Panic, Defer e RecoverRecapitulação - Analisador de Arquivos3Ponteiros e Memória
Fundamentos de Ponteiros em GoPonteiros para StructsPassagem por Valor vs. ReferênciaA Função new()Coleta de Lixo em GoRecapitulação - Construtor de Lista Ligada6Polimorfismo em Go
Polimorfismo via InterfacesDuck Typing em GoRegras de Satisfação de InterfacesColeções PolimórficasInjeção de DependênciasRecapitulação - Processador de Pagamentos9Concorrência e OOP
Fundamentos de GoroutinesCanais e comunicaçãoCanais com e sem bufferInstrução selectsync.Mutex e sync.RWMutexsync.WaitGroupDesign de structs thread-safeRevisão - Worker Pool12Conceitos Avançados de OOP
Padrão de Opções FuncionaisPadrão Builder em GoEncadeamento de MétodosAliases de Tipos vs. DefiniçõesNoções Básicas de ReflectionVisão Geral da Geração de CódigoPratique por conta própria: Compilador de Go online