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-фреймворки также используют генерацию для создания типобезопасных запросов к базам данных или клиентского кода.

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

challenge icon

Задание

Легко

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

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

  • generator.go: Create логику генерации кода.

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

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

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

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

    const (
        Value1 TypeName = iota
        Value2
        Value3
    )

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

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

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

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

  • Строка 1: имя типа enum
  • Строка 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, который можно сохранить в файл и скомпилировать вместе с остальной частью проекта.

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

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Проверьте себя

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

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

Потренируйтесь самостоятельно: Онлайн-компилятор Go