Обзор генерации кода
Часть раздела Объектно-ориентированное программирование путешествия по 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-фреймворки также используют генерацию для создания типобезопасных запросов к базе данных или клиентского кода.
Ключевое преимущество перед рефлексией заключается в том, что сгенерированный код проверяется компилятором. Ошибки выявляются на этапе сборки, а не во время выполнения. Компромисс заключается в дополнительном этапе сборки и необходимости управления сгенерированными файлами в вашем репозитории. Для паттернов ООП, требующих повторяющихся реализаций для множества типов, генерация кода часто обеспечивает наилучший баланс безопасности и удобства.
Задание
ЛегкоДавайте создадим генератор кода, который создает методы 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
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
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Цепочки вызовов методовПсевдонимы и определения типовОсновы рефлексииОбзор генерации кода