Menu
Coddy logo textTech

Visão Geral de 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 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 em que reduz o código boilerplate repetitivo.

O Go fornece o comando go generate para automatizar a geração de código. Você coloca uma diretiva de comentário especial em seu arquivo de origem, e a execução do 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 que é compilado com o seu programa.

Ferramentas comuns de geração de código no ecossistema Go incluem stringer para métodos de string de enum, mockgen para criar mocks de teste a partir de interfaces e protoc para gerar código a partir de definições de Protocol Buffer. Muitos ORMs e frameworks de API também usam geração para criar consultas de banco de dados seguras quanto ao tipo ou código de cliente.

A principal vantagem sobre a reflexão é que o código gerado é verificado pelo compilador. Os erros são capturados no momento da compilação (build time), não em tempo de execução (runtime). A contrapartida é uma etapa extra de build e arquivos gerados para gerenciar em seu repositório. Para padrões de POO 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.

challenge icon

Desafio

Fácil

Vamos construir um gerador de código que cria métodos String() para tipos semelhantes a enums! Embora não possamos executar o go generate real neste ambiente, você simulará o que as ferramentas de geração de código fazem — produzindo código-fonte Go programaticamente com base em definições de tipo.

Você organizará seu código em dois arquivos:

  • generator.go: Crie sua lógica de geração de código.

    Construa uma struct EnumType que represente uma definição de tipo semelhante a um enum com um Name (string) e Values (slice de strings representando os nomes das constantes).

    Crie uma função GenerateStringMethod(e EnumType) string que produza código-fonte Go para um método String(). 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) string que produza as declarações de constantes usando iota:

    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 de enum.

    Leia o nome do tipo enum, depois leia uma contagem de valores seguida por cada nome de valor. Passe essas informações para o seu gerador e imprima o código gerado completo.

As seguintes entradas serão fornecidas:

  • Linha 1: Nome do tipo enum
  • Linha 2: Número de valores
  • Linhas seguintes: Cada nome de valor

Por exemplo, dado:

Status
3
Pending
Active
Completed

Sua 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 dado:

Priority
2
Low
High

Sua 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 com segurança de tipos em tempo de compilação, em vez de usar reflexão em tempo de 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.

Folha de consulta

A geração de código cria código-fonte Go em tempo de compilação em vez de inspecionar tipos em tempo de execução, proporcionando segurança de tipos e melhor desempenho, ao mesmo tempo que reduz o código repetitivo (boilerplate).

O comando go generate automatiza a geração de código usando diretivas de comentários especiais:

//go:generate stringer -type=Status

type Status int

const (
    Pending Status = iota
    Active
    Completed
)

Executar go generate ./... invoca a ferramenta especificada (como o stringer), que cria um novo arquivo contendo o código gerado que é compilado com o seu programa.

Ferramentas comuns de geração de código incluem:

  • stringer - gera métodos String() para tipos enum
  • mockgen - cria mocks de teste a partir de interfaces
  • protoc - gera código a partir de definições de Protocol Buffer

Principais vantagens sobre a reflexão (reflection):

  • O código gerado é verificado pelo compilador
  • Erros são capturados em tempo de compilação, não em tempo de execução
  • Melhor desempenho

As desvantagens incluem uma etapa extra de compilação e arquivos gerados para gerenciar em seu repositório.

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
}
quiz iconTeste seus conhecimentos

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