То есть у нас есть какой-то list, например []News, и нам нужно собрать все ID в слайс для дальнейшего использования.
Такие простые куски кода разрастаются по проекту, но не несут никакой смысловой нагрузки. Или рассмотрим другой пример – преобразование слайса в мап по ID:
Теперь мы можем упросить основной код и использовать новые методы:
nn:=NewsList(list)
ids:=nn.IDs()
idx:=nn.Index()
Плюсы:
Утилитарный код ушел из бизнес-логики и был стандартизирован.
Появился новый тип, для которого мы можем добавить больше методов.
Неочевидный плюс: стандартизируем нейминг, не нужно больше придумывать имена переменным в утилитарном коде.
Минусы:
Код все еще нужно писать.
Давайте попробуем убрать минусы и добавим в наш файл:
//go:generate colgen//colgen:News
Данная конструкция создаст новый файл с суффиксом _colgen.go, в котором будет базовый тип для структуры и два метода: IDs() и Index() (если есть поле ID).
В результате мы получим базовый тип и два метода: NewsIDs() и IndexByNewsID().
Допустим, у новости есть теги TagIDs, и нам из списка надо получить уникальные теги. А у тегов есть поле alias, и нам нужно сделать индекс по нему.
Теперь мы можем сконцентрироваться на решении практических задач, а утилитарный код отдать на откуп генератору.
Единственное, на что надо обращать внимание: код перед вызовом генератора должен компилироваться, потому что идет парсинг go файлов через AST.
Group(Field)
Можно сгруппировать слайс по определенному полю. На выходе получим такой код //colgen:News:Group(CategoryID):
NewNews – конструктор, который преобразует структуру из одного слоя в другой.
NewNewsList – конструктор для слайса.
В силу многослойной архитектуры подобного кода будет встречаться в проекте много.
Давайте попробуем решить проблему с NewNewsList. Расширим наш последний вызов //go:generate
Если NewNews принимает *db.News, то необходимо использовать MapP(db).
Если нужны приватные конструкторы, то используем mapp/map вместо MapP/Map
Если название структуры в db слое отличается, то используем полный путь: Map(db.News)
Если структура не описана в базовой генерации //colgen:News,Tags,..., то конструктор будет возвращать []News вместо NewsList
Для стабильной генерации добавляем импорт в файл через флаг: -imports=newsportal/pkg/db
Если Map/MapP лежат в другом пакете, то нужно добавить этот пакет в импорт через запятую и добавить название пакета через -funcpkg=<pkg>.
Не забудем добавить в проект те самые два дженерика:
// MapP converts slice of type T to slice of type M with given converter with pointers.funcMapP[T, Many](a []T, ffunc(*T) *M) []M {
n:= make([]M, len(a))
fori:=rangea {
n[i] = *f(&a[i])
}
returnn}
// Map converts slice of type T to slice of type M with given converter.funcMap[T, Many](a []T, ffunc(T) M) []M {
n:= make([]M, len(a))
fori:=rangea {
n[i] = f(a[i])
}
returnn}
Рекомендуется иметь один colgen на пакет. Директивы //go:generate colgen можно размещать в файле collection.go.
Если все сломалось, то удаляйте файл _colgen.go и вызывайте генератор снова. Но! Если функции генератора уже используются в коде, то при удалении файла будет нерабочий код и генератор не запустится.
Таким образом можно описать генерацию конструкторов для слайсов. Но что делать с конструкторами?
Генерация сниппетов в инлайн режиме
//go:generate colgen...//colgen@NewNews(db)
При генерации директива //colgen@NewNews(db)будет заменена в этом файле на следующий код:
Когда мы говорим о доменном слое, то нам подходит первый вариант.
Когда мы говорим о слое с апи, нам подходит второй вариант.
На сложных объектах вы будете получать невалидный сниппет. Но базовая идея заключается в том, чтобы получить сниппет, а потом его отредактировать как нужно. Все конструкторы уникальны, поэтому невозможно сделать их стабильную генерацию. Но генерация сниппетов может помочь!
И коротко об embed структурах в доменном слое: нет особого логического смысла дублировать поля, поэтому можно просто встроить структуру и допустить меньше ошибок в будущем при ее изменении.
Теперь попробуем описать шаги для максимальной генерации:
Делаем базовые типы и методы.
Создаем сниппеты конструктора через инлайн режим.
Добавляем себе в проект Map/MapP дженерики.
В последнюю очередь добавляем Map/MapP генераторы. Если их добавить в пункте 1, то будет некомпилируемый код из-за отсутствия функций конструктора.
Работа с LLM
Для работы с DeepSeek или Claude у вас должен быть API ключ. $2 или $5 придется потратить. Локальный режим не поддерживается, PR приветствуются.
Работа с LLM идет в рамках файла, именно его содержимое (+тест, если применимо) + промт отправляется на сервер. Зависимые типы / пакеты или файлы не отправляются.
Есть три режима работы:
//colgen@ai:review – код ревью с идеоматичным промтом. Результат будет рядом в файле с суффиксом .md. Бонусом будет предложена отсутствующая документация у методов и структур.
//colgen@ai:readme – генерация README.md по текущему файлу. Результат будет рядом в файле с суффиксом .md.
//colgen@ai:tests Генерация тестов с нуля или путем дописывания файла с тестами по образу и подобию текущих тестов в файле _test.go.
Для выбора LLM в конце нужно добавить (deepseek) или (claude). DeepSeek используется по умолчанию, поэтому его можно не добавлять.
Примеры:
//go:generate colgen
//colgen@ai:readme // makes readme using deepseek by default
//colgen@ai:tests(deepseek) // makes tests using deepseek explicitly
//colgen@ai:review(claude) // makes review using claude