Menu
Coddy logo textTech

Обзор генерации кода

Часть раздела Объектно-ориентированное программирование путешествия по GO на Coddy — урок 84 из 107.

Генерация кода — это альтернатива рефлексии, которая создает исходный код на Go во время компиляции, а не проверяет типы во время выполнения. Этот подход обеспечивает типобезопасность и лучшую производительность, при этом сокращая количество повторяющегося шаблонного кода.

Go предоставляет команду go generate для автоматизации генерации кода. Вы помещаете специальную директиву в виде комментария в исходный файл, и выполнение go generate запускает указанный инструмент:

//go:generate stringer -type=Status

type Status int

const (
    Pending Status = iota
    Active
    Completed
)

Запуск go generate ./... вызывает инструмент stringer, который создает новый файл, содержащий метод String() для типа Status. Сгенерированный код представляет собой обычный Go, который компилируется вместе с вашей программой.

Распространенные инструменты генерации кода в экосистеме Go включают stringer для строковых методов перечислений, mockgen для создания тестовых моков из интерфейсов и protoc для генерации кода из определений Protocol Buffer. Многие ORM и API-фреймворки также используют генерацию для создания типобезопасных запросов к базе данных или клиентского кода.

Ключевое преимущество перед рефлексией заключается в том, что сгенерированный код проверяется компилятором. Ошибки выявляются на этапе сборки, а не во время выполнения. Компромисс заключается в дополнительном этапе сборки и необходимости управления сгенерированными файлами в вашем репозитории. Для паттернов ООП, требующих повторяющихся реализаций для множества типов, генерация кода часто обеспечивает наилучший баланс безопасности и удобства.

challenge icon

Задание

Легко

Давайте создадим генератор кода, который создает методы String() для типов, подобных перечислениям (enum)! Хотя мы не можем запустить настоящий go generate в этой среде, вы будете имитировать то, что делают инструменты генерации кода — программно создавать исходный код Go на основе определений типов.

Вы организуете свой код в двух файлах:

  • generator.go: Создайте логику генерации кода.

    Создайте структуру EnumType, которая представляет определение типа, похожего на enum, с полями Name (строка) и Values (срез строк, представляющих имена констант).

    Создайте функцию GenerateStringMethod(e EnumType) string, которая генерирует исходный код Go для метода String(). Сгенерированный код должен соответствовать следующему шаблону:

    func (e TypeName) String() string {
        switch e {
        case Value1:
            return "Value1"
        case Value2:
            return "Value2"
        default:
            return "Unknown"
        }
    }

    Также создайте функцию GenerateConstants(e EnumType) string, которая создает объявления констант с использованием iota:

    const (
        Value1 TypeName = iota
        Value2
        Value3
    )

    Наконец, создайте GenerateFullCode(e EnumType) string, которая объединяет оба результата с заголовком-комментарием, указывающим, что это сгенерированный код:

    // Code generated by enum generator. DO NOT EDIT.
    
    [constants]
    
    [string method]
  • main.go: Используйте свой генератор для создания кода для различных типов перечислений.

    Считайте имя типа перечисления, затем считайте количество значений, а после — каждое имя значения. Передайте эту информацию в свой генератор и выведите полный сгенерированный код.

Будут предоставлены следующие входные данные:

  • Строка 1: Имя типа перечисления
  • Строка 2: Количество значений
  • Следующие строки: Каждое имя значения

Например, для входных данных:

Status
3
Pending
Active
Completed

Ваш вывод должен быть таким:

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

И для входных данных:

Priority
2
Low
High

Ваш вывод должен быть таким:

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

Это упражнение демонстрирует основную концепцию таких инструментов, как stringer — генерацию повторяющегося, но типобезопасного кода во время компиляции, вместо использования рефлексии во время выполнения. Ваш генератор создает валидный код Go, который можно было бы сохранить в файл и скомпилировать вместе с остальной частью проекта.

Шпаргалка

Генерация кода создает исходный код Go во время компиляции, а не проверяет типы во время выполнения, обеспечивая типобезопасность и лучшую производительность при одновременном сокращении шаблонного кода.

Команда go generate автоматизирует генерацию кода с помощью специальных директив в комментариях:

//go:generate stringer -type=Status

type Status int

const (
    Pending Status = iota
    Active
    Completed
)

Запуск go generate ./... вызывает указанный инструмент (например, stringer), который создает новый файл с сгенерированным кодом, компилируемым вместе с вашей программой.

Распространенные инструменты генерации кода включают:

  • stringer — генерирует методы String() для перечисляемых типов (enum)
  • mockgen — создает тестовые моки из интерфейсов
  • protoc — генерирует код из определений Protocol Buffer

Ключевые преимущества перед рефлексией:

  • Сгенерированный код проверяется компилятором
  • Ошибки обнаруживаются во время сборки, а не во время выполнения
  • Лучшая производительность

К недостаткам относятся дополнительный этап сборки и необходимость управления сгенерированными файлами в вашем репозитории.

Попробуйте сами

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	reader := bufio.NewReader(os.Stdin)

	// Чтение имени типа перечисления
	typeName, _ := reader.ReadString('\n')
	typeName = strings.TrimSpace(typeName)

	// Чтение количества значений
	countStr, _ := reader.ReadString('\n')
	count, _ := strconv.Atoi(strings.TrimSpace(countStr))

	// Чтение каждого имени значения
	values := make([]string, count)
	for i := 0; i < count; i++ {
		value, _ := reader.ReadString('\n')
		values[i] = strings.TrimSpace(value)
	}

	// TODO: Создать EnumType с прочитанными данными

	// TODO: Сгенерировать и вывести полный код, используя GenerateFullCode
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование