Menu
Coddy logo textTech

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.

challenge icon

Desafio

Fácil

Vamos 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 EnumType que represente uma definição de tipo semelhante a uma enumeração, com um Name (string) e Values (slice de strings que representa 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 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
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, dadas as entradas:

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 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
}
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

Pratique por conta própria: Compilador de Go online