Обзор генерации кода
Часть раздела Объектно-ориентированное программирование путешествия по 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, требующих повторяющихся реализаций для множества типов, генерация кода часто обеспечивает наилучший баланс между безопасностью и удобством.
Задание
ЛегкоДавайте создадим генератор кода, который создаёт методы 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
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП в Go
Внешние файлыРабочее пространство и модули GoПакеты и импортыЭкспортируемые и неэкспортируемые именаВведение в ООП в GoСтруктуры как классыОпределение методов структурПолучатели-указатели и получатели-значенияИнициализация структурФункции-конструкторыИтоги — Простой калькулятор4Интерфейсы
Введение в интерфейсыНеявная реализацияИнтерфейс как контрактПустой интерфейс (any)Утверждение типаПереключатель типовКомпозиция интерфейсовИнтерфейсы Stringer и ErrorПовторение: Калькулятор фигур7Инкапсуляция
Экспортируемые и неэкспортируемые поляИнкапсуляция на уровне пакетовГеттеры и сеттерыСокрытие информации в GoИтоги — Записи о студентах10Обобщения (Generics) (Go 1.18+)
Введение в GenericsПараметры типовОграничения типовОбобщенные структурыОбходной путь для обобщенных методовИтоги — Обобщенная коллекция13Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн Abstract FactoryПаттерн ObserverПаттерн Strategy2Глубокое погружение в типы и структуры
Базовые и составные типыОпределение пользовательских типовТеги структурАнонимные структурыВложенные структурыНулевые значения и значения по умолчаниюПовторение — Контактная книга5Композиция вместо наследования
Почему в Go нет наследованияОсновы встраивания структурПродвижение методовВстраивание нескольких структурВстраивание против агрегацииЗатенение встроенных методовИтоги — Иерархия сотрудников8Обработка ошибок и ООП
Интерфейс errorПользовательские типы ошибокОбертывание ошибок (fmt.Errorf)Sentinel-ошибкиerrors.Is() и errors.As()Panic, Defer и RecoverИтоги — Парсер файлов11Стандартная библиотека и ООП
io.Reader и io.Writersort.InterfaceИнтерфейс fmt.Stringerencoding/json со структурамиИнтерфейс http.HandlerПовторение: модели REST API14Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикMiddleware как Декоратор3Указатели и память
Основы указателей в GoУказатели на структурыПередача по значению и по ссылкеФункция new()Сборка мусора в GoПовторение: Конструктор связного списка6Полиморфизм в Go
Полиморфизм через интерфейсыУтиная типизация в GoПравила реализации интерфейсовПолиморфные коллекцииВнедрение зависимостейИтоги — Обработчик платежей9Конкурентность и ООП
Основы горутинКаналы и взаимодействиеБуферизованные и небуферизованные каналыОператор selectsync.Mutex и sync.RWMutexsync.WaitGroupПроектирование потокобезопасных структурПовторение — Worker Pool12Продвинутые концепции ООП
Паттерн функциональных опцийПаттерн Builder в GoЦепочки вызовов методовПсевдонимы и определения типовОсновы рефлексииОбзор генерации кодаПотренируйтесь самостоятельно: Онлайн-компилятор Go