Иконка подарка

Весенняя распродажа! Скидка 15% по промокоду

до 01.04.2026

Генерация слайсов в Go - практическое руководство по slice generator

27 марта 2026
Автор

Олег Марков

Введение

Генерация слайсов (slice-generator) чаще всего всплывает в двух контекстах:

  1. Как «генератор слайсов» — подход или утилита, которая создает, наполняет и преобразует срезы по заданным правилам.
  2. Как кодогенерация вокруг слайсов — когда вы генерируете вспомогательные функции и обертки, чтобы не писать однотипный код руками.

С ростом кода на Go, особенно до появления дженериков, разработчики часто уходили в генерацию кода для работы со слайсами: фильтрация, маппинг, поиск, уникализация и так далее. Сейчас, с появлением дженериков, часть таких задач упростилась, но генерация по-прежнему полезна: она позволяет избавиться от рутины и сделать работу со слайсами более предсказуемой и типобезопасной.

В этой статье вы увидите, как подойти к теме «slice-generator» системно:

  • как проектировать функции-генераторы слайсов;
  • как использовать шаблоны и go generate для автогенерации кода;
  • как собирать типичные операции над слайсами в единый «генераторский» подход;
  • как контролировать выделения памяти и производительность;
  • как применить это в реальных задачах.

Смотрите, я буду показывать примеры кода, каждый из которых снабжен комментариями. Это поможет вам шаг за шагом разобраться, что и зачем происходит.


Базовые принципы генерации слайсов

Что такое генерация слайсов

Под генерацией слайсов будем понимать:

  • создание слайса по некоторому правилу;
  • наполнение слайса значениями, иногда «лениво»;
  • построение новых слайсов на основе существующих (map, filter, group, chunk и т.п.);
  • автоматическую генерацию кода для таких операций.

Важно понимать, что в Go нет «ленивых генераторов» в духе Python yield. Зато есть:

  • функции, которые возвращают готовый слайс;
  • функции-итераторы, которые заполняют переданный слайс;
  • кодогенерация с помощью go generate и шаблонов.

Давайте разберемся на простых примерах, а потом перейдем к более продвинутым инструментам.

Напоминание — как устроен слайс в Go

Прежде чем строить генератор, важно помнить, из чего вообще состоит слайс:

  • указатель на массив (под капотом);
  • длина (len);
  • емкость (cap).

Пример создания и расширения слайса:

package main

import "fmt"

func main() {
    // Создаем пустой слайс целых чисел длиной 0 и емкостью 0
    var a []int

    // Добавляем элементы с помощью append
    a = append(a, 1) // Здесь Go создаст массив под капотом
    a = append(a, 2, 3, 4)

    fmt.Println(a)      // [1 2 3 4]
    fmt.Println(len(a)) // 4 - текущее количество элементов
    fmt.Println(cap(a)) // емкость может быть больше или равна длине
}

Когда вы строите генератор слайсов, вы почти всегда будете управлять длиной и емкостью. Это сильно влияет на производительность.


Простые функции-генераторы слайсов

Функции, которые создают слайс по правилу

Самый базовый вариант slice-generator — функция, которая по входным данным строит слайс.

Например, генератор арифметической прогрессии:

// IntRange генерирует слайс целых чисел от start до end с шагом step.
// Если step <= 0, функция вернет пустой слайс.
func IntRange(start, end, step int) []int {
    // Проверяем аргументы
    if step <= 0 || start >= end {
        // В таких случаях удобно возвращать nil или пустой слайс
        return nil
    }

    // Оцениваем количество элементов, чтобы заранее выделить память
    size := (end - start + step - 1) / step // округление вверх

    // Создаем слайс нужной длины
    res := make([]int, 0, size)

    // Наполняем слайс значениями
    for v := start; v < end; v += step {
        res = append(res, v)
    }

    return res
}

Здесь я заранее вычисляю size и создаю слайс с нужной емкостью. Это типичная часть любой «генерации» слайса: вы стараетесь избежать лишних realLOC.

Теперь вы увидите, как это выглядит в коде при использовании:

func main() {
    // Сгенерируем числа от 0 до 10 с шагом 2
    even := IntRange(0, 10, 2)
    // even == []int{0, 2, 4, 6, 8}

    // Сгенерируем пустой слайс при некорректном шаге
    invalid := IntRange(0, 10, 0)
    // invalid == nil
}

Генерация слайса из функции-источника

Иногда вы хотите сказать: «дай мне N значений, каждое получено вызовом некой функции». Например, сгенерировать N случайных чисел.

Показываю, как это реализовано на практике:

package main

import (
    "fmt"
    "math/rand"
    "time"
)

// GenerateSlice создает слайс длиной n, заполняя его значениями,
// которые возвращает функция generator.
func GenerateSlice[T any](n int, generator func(i int) T) []T {
    if n <= 0 {
        return nil
    }

    // Создаем слайс нужного размера
    res := make([]T, n)

    // Заполняем каждый элемент
    for i := 0; i < n; i++ {
        // Передаем индекс, чтобы генератор мог им воспользоваться
        res[i] = generator(i)
    }

    return res
}

func main() {
    rand.Seed(time.Now().UnixNano())

    // Генерируем 5 случайных чисел
    randoms := GenerateSlice(5, func(i int) int {
        // i здесь можно использовать, например, для смещения
        return rand.Intn(100)
    })

    fmt.Println(randoms)

    // Генерируем 10 строк по шаблону
    strings := GenerateSlice(10, func(i int) string {
        return fmt.Sprintf("item-%d", i)
    })

    fmt.Println(strings)
}

Смотрите, GenerateSlice — это универсальный slice-generator. Вы передаете:

  • размер слайса;
  • функцию, которая «порождет» каждый элемент.

Вы можете вынести такую функцию в отдельный пакет и использовать как строительный блок во многих местах.


Генерация на основе существующих слайсов

Очень часто «генерация» нового слайса происходит на основе старого. Например:

  • фильтрация;
  • преобразование типа;
  • группировка.

Это по сути и есть генераторы: они берут исходные данные и порождают новую последовательность.

Map для слайсов

Давайте разберемся на примере функции Map, которая строит новый слайс, применяя функцию к каждому элементу исходного.

// Map применяет функцию f ко всем элементам src и возвращает новый слайс.
func Map[S ~[]E, E any, R any](src S, f func(E) R) []R {
    // Предполагаем, что результирующий слайс будет той же длины
    res := make([]R, len(src))

    for i, v := range src {
        // Применяем функцию-преобразователь
        res[i] = f(v)
    }

    return res
}

Здесь:

  • S ~[]E — обобщенный тип, который «ведет себя как» слайс элементов E. Это удобно, если вы хотите работать с алиасами слайсов.
  • E any — тип элемента исходного слайса;
  • R any — тип элемента результирующего слайса.

Теперь давайте посмотрим, что происходит в следующем примере использования:

func main() {
    ints := []int{1, 2, 3, 4}

    // Умножаем каждый элемент на 10
    tens := Map(ints, func(v int) int {
        return v * 10
    })
    // tens == []int{10, 20, 30, 40}

    // Преобразуем int в строку
    strs := Map(ints, func(v int) string {
        return fmt.Sprintf("val-%d", v)
    })
    // strs == []string{"val-1", "val-2", "val-3", "val-4"}
}

Это и есть типичный пример slice-generator «на основе» другого слайса.

Filter для слайсов

Теперь покажу генерацию слайса по условию — фильтрацию:

// Filter возвращает новый слайс с элементами src,
// для которых предикат pred вернул true.
func Filter[S ~[]E, E any](src S, pred func(E) bool) []E {
    // Создаем слайс с нулевой длиной, но емкостью как у src.
    // Это оптимизация: мы предполагаем, что отбор не будет очень жестким.
    res := make([]E, 0, len(src))

    for _, v := range src {
        if pred(v) {
            // Добавляем только подходящие элементы
            res = append(res, v)
        }
    }

    return res
}

Использование:

func main() {
    ints := []int{1, 2, 3, 4, 5, 6}

    // Оставим только четные
    even := Filter(ints, func(v int) bool {
        return v%2 == 0
    })
    // even == []int{2, 4, 6}
}

Обратите внимание, как этот фрагмент кода решает задачу генерации нового слайса:

  • мы заранее задаем емкость;
  • наполняем только тем, что проходит проверку.

Генерация слайсов через кодогенерацию

Когда дженериков не было, slice-generator чаще всего означал:

  • набор шаблонов (.gotmpl или template.go);
  • go generate, который по этим шаблонам создавал специализированные функции.

Сейчас дженерики позволяют многое сделать без генерации файлов, но кодогенерация все равно полезна:

  • при необходимости избавиться от дженериков (например, для старых версий Go или специфичных окружений);
  • для генерации кода с инлайном и ручной оптимизацией;
  • для случаев, когда шаблоны легче поддерживать, чем сложные generic-конструкции.

Базовая схема go generate для слайс-генераторов

Давайте разберемся на примере.

Вы хотите сгенерировать набор функций Map, Filter, Reduce для конкретного типа int. Вместо того, чтобы писать руками, вы создаете шаблон и генератор.

Структура проекта:

  • slice_templates/ — папка с шаблонами;
  • slice_templates/int_slice.go.tmpl — файл-шаблон;
  • cmd/slice-generator/main.go — утилита генерации;
  • generated/int_slice_gen.go — результат генерации.

Пример шаблона для генерации кода над слайсом

Содержимое slice_templates/int_slice.go.tmpl:

package generated

// Этот файл сгенерирован автоматически.
// Не редактируйте его вручную.

type {{.TypeName}}Slice []{{.TypeName}}

// Map применяет функцию f ко всем элементам слайса
// и возвращает новый слайс.
func (s {{.TypeName}}Slice) Map(f func(v {{.TypeName}}) {{.TypeName}}) {{.TypeName}}Slice {
    res := make({{.TypeName}}Slice, len(s))
    for i, v := range s {
        res[i] = f(v)
    }
    return res
}

// Filter фильтрует слайс по предикату pred.
func (s {{.TypeName}}Slice) Filter(pred func(v {{.TypeName}}) bool) {{.TypeName}}Slice {
    res := make({{.TypeName}}Slice, 0, len(s))
    for _, v := range s {
        if pred(v) {
            res = append(res, v)
        }
    }
    return res
}

Здесь я размещаю пример, чтобы вам было проще понять:

  • {{.TypeName}} — подстановочное место, в которое генератор подставит, например, Int.
  • Из этого шаблона получится тип IntSlice со своими методами.

Пример утилиты slice-generator

Теперь сделаем простую утилиту, которая возьмет шаблон и создаст файл. Смотрите, я покажу вам, как это работает:

// cmd/slice-generator/main.go
package main

import (
    "log"
    "os"
    "path/filepath"
    "text/template"
)

// Config описывает параметры генерации.
type Config struct {
    TypeName string // Например Int или String
    GoType   string // Соответствующий реальный тип - int, string
}

func main() {
    // Здесь мы жестко задаем параметры.
    // В реальном проекте их можно читать из флагов или конфигурации.
    cfg := Config{
        TypeName: "Int",
        GoType:   "int",
    }

    // Загружаем шаблон
    tmplPath := filepath.Join("slice_templates", "int_slice.go.tmpl")
    tmpl, err := template.ParseFiles(tmplPath)
    if err != nil {
        log.Fatalf("parse template: %v", err)
    }

    // Создаем выходной файл
    outPath := filepath.Join("generated", "int_slice_gen.go")
    outFile, err := os.Create(outPath)
    if err != nil {
        log.Fatalf("create output: %v", err)
    }
    defer outFile.Close()

    // Выполняем шаблон с подстановкой данных
    if err := tmpl.Execute(outFile, cfg); err != nil {
        log.Fatalf("execute template: %v", err)
    }
}

Это минимальный slice-generator в смысле «генератор кода для работы со слайсами». Он берет один шаблон, подставляет TypeName и GoType и создает конкретный файл.

Интеграция с go generate

Чтобы запускать этот генератор удобно, добавим комментарий //go:generate в нужный пакет.

Например, в generated/doc.go:

package generated

//go:generate go run ../cmd/slice-generator/main.go

Теперь команда:

go generate ./generated

запустит нашу утилиту и обновит файл int_slice_gen.go.


Практические паттерны для slice-generator

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

Паттерн: генерация «ленивого» набора данных в слайс

Иногда вам нужно получить слайс, но источник — это, по сути, поток: чтение файлов, запросы к БД, чтение каналов. Вы хотите инкапсулировать это в генератор.

Простой пример: чтение строк из канала в слайс до закрытия:

// CollectStrings читает все строки из канала ch и возвращает их как слайс.
func CollectStrings(ch <-chan string) []string {
    // Начальная емкость можно оценить, если известна
    res := make([]string, 0)

    for s := range ch {
        // Добавляем по мере поступления
        res = append(res, s)
    }

    return res
}

Этот подход можно обобщить:

// Collect собирает значения из канала ch в слайс.
// Функция завершается, когда канал закрывается.
func Collect[T any](ch <-chan T) []T {
    res := make([]T, 0)
    for v := range ch {
        res = append(res, v)
    }
    return res
}

Такой генератор полезен, когда вы хотите «материализовать» поток данных в слайс, чтобы дальше работать уже с обычным массивом.

Паттерн: chunk-генератор (разбиение на батчи)

Частая задача: разбить большой слайс на части по N элементов (батчи). Это генерация набора слайсов по исходному.

// Chunk разбивает слайс src на части размером size.
// Последний слайс может быть короче.
func Chunk[T any](src []T, size int) [][]T {
    if size <= 0 {
        return nil
    }

    // Оцениваем количество чанков
    n := (len(src) + size - 1) / size
    res := make([][]T, 0, n)

    for i := 0; i < len(src); i += size {
        end := i + size
        if end > len(src) {
            end = len(src)
        }

        // Создаем новый слайс и копируем данные
        chunk := make([]T, end-i)
        copy(chunk, src[i:end])

        res = append(res, chunk)
    }

    return res
}

Здесь важно, что мы копируем данные в новый слайс, а не делаем срез оригинального. Это делает чанки независимыми: изменения в одном чанке не повлияют на исходный массив. При генерации таких вспомогательных функций вы всегда решаете, хотите ли вы «разделяемую» память или отдельные копии.


Производительность и память при генерации слайсов

Когда вы пишете slice-generator, вы фактически управляете:

  • количеством realLOC через make и append;
  • копированием памяти;
  • количеством проходов по данным.

Предварительная емкость

Покажу вам, как это реализовано на практике, когда вы заранее знаете длину:

func GenerateSquares(n int) []int {
    if n <= 0 {
        return nil
    }

    // Создаем слайс сразу нужной длины
    res := make([]int, n)

    for i := 0; i < n; i++ {
        res[i] = i * i
    }

    return res
}

Здесь нет append — мы просто присваиваем по индексу. Это самый предсказуемый вариант по памяти.

Если длину заранее оценить сложно, можно задать начальную емкость:

func GenerateUnknownCount(source []int) []int {
    // Предполагаем, что половина значений нам подойдет
    res := make([]int, 0, len(source)/2)

    for _, v := range source {
        if v%3 == 0 {
            res = append(res, v)
        }
    }

    return res
}

Избегание лишних копий

Иногда вы можете генерировать слайс, который ссылается на тот же массив, что и входной, без копирования.

Пример: срез по условию подряд идущих элементов (только до первого неподходящего):

// TakeWhile возвращает префикс src, в котором все элементы
// удовлетворяют предикату pred. Возвращаемый слайс разделяет
// базовый массив с src.
func TakeWhile[T any](src []T, pred func(T) bool) []T {
    i := 0
    for ; i < len(src); i++ {
        if !pred(src[i]) {
            break
        }
    }
    // Здесь мы возвращаем "слайс от начала до i"
    return src[:i]
}

Здесь генерация нового слайса происходит «логически», но физически создается лишь структура слайса, а не новый массив.

В генераторах кода вы можете явно документировать такие места, чтобы разработчики понимали, что изменение результата может повлиять на исходный слайс.


Проектирование собственного slice-generator пакета

Теперь давайте посмотрим, как можно организовать небольшой пакет, который будет выполнять роль универсального генератора операций над слайсами.

Структура пакета

Представим пакет slice, в котором будут:

  • функции генерации (Generate, Range, Repeat);
  • функции преобразования (Map, Filter, Chunk);
  • возможно, обертка-тип с методами для удобной chaining-цепочки.

Примеры функций генерации

package slice

// Range генерирует целочисленный слайс от start до end (не включая end)
// с шагом step.
func Range(start, end, step int) []int {
    if step <= 0 || start >= end {
        return nil
    }
    size := (end - start + step - 1) / step
    res := make([]int, 0, size)
    for v := start; v < end; v += step {
        res = append(res, v)
    }
    return res
}

// Repeat создает слайс длиной n, заполненный значением val.
func Repeat[T any](val T, n int) []T {
    if n <= 0 {
        return nil
    }
    res := make([]T, n)
    for i := range res {
        res[i] = val
    }
    return res
}

// FromFunc создает слайс длиной n, каждый элемент которого
// вычисляется с помощью функции f.
func FromFunc[T any](n int, f func(i int) T) []T {
    if n <= 0 {
        return nil
    }
    res := make([]T, n)
    for i := 0; i < n; i++ {
        res[i] = f(i)
    }
    return res
}

Примеры функций преобразования

// Map применяет функцию f к каждому элементу src.
func Map[S ~[]E, E any, R any](src S, f func(E) R) []R {
    res := make([]R, len(src))
    for i, v := range src {
        res[i] = f(v)
    }
    return res
}

// Filter отбирает элементы по предикату pred.
func Filter[S ~[]E, E any](src S, pred func(E) bool) []E {
    res := make([]E, 0, len(src))
    for _, v := range src {
        if pred(v) {
            res = append(res, v)
        }
    }
    return res
}

// Chunk делит слайс на чанки фиксированного размера.
func Chunk[T any](src []T, size int) [][]T {
    if size <= 0 {
        return nil
    }
    n := (len(src) + size - 1) / size
    res := make([][]T, 0, n)
    for i := 0; i < len(src); i += size {
        end := i + size
        if end > len(src) {
            end = len(src)
        }
        chunk := make([]T, end-i)
        copy(chunk, src[i:end])
        res = append(res, chunk)
    }
    return res
}

Обертка для цепочек операций

Если вы хотите более «декларативный» стиль, можно сделать тип-обертку.

// Stream оборачивает слайс и позволяет вызывать цепочки методов.
type Stream[T any] struct {
    data []T
}

// From создает Stream из слайса.
func From[T any](data []T) Stream[T] {
    return Stream[T]{data: data}
}

// Map создает новый Stream с результатом применения функции f.
func (s Stream[T]) Map(f func(T) T) Stream[T] {
    res := make([]T, len(s.data))
    for i, v := range s.data {
        res[i] = f(v)
    }
    return Stream[T]{data: res}
}

// Filter создает новый Stream, отфильтрованный по pred.
func (s Stream[T]) Filter(pred func(T) bool) Stream[T] {
    res := make([]T, 0, len(s.data))
    for _, v := range s.data {
        if pred(v) {
            res = append(res, v)
        }
    }
    return Stream[T]{data: res}
}

// Collect возвращает внутренний слайс.
func (s Stream[T]) Collect() []T {
    return s.data
}

Теперь вы увидите, как это выглядит в коде:

func main() {
    src := []int{1, 2, 3, 4, 5, 6}

    // Строим цепочку генерации нового слайса
    res := From(src).
        Filter(func(v int) bool { return v%2 == 0 }). // четные
        Map(func(v int) int { return v * 10 }).       // умножаем на 10
        Collect()

    // res == []int{20, 40, 60}
}

С точки зрения пользователя это выглядит как своеобразный slice-generator на уровне API пакета.


Когда использовать дженерики, а когда кодогенерацию

Тема «slice-generator» неизбежно пересекается с выбором: писать generic-функции или генерировать конкретный код.

Кратко:

  • Используйте дженерики, когда:

    • вы поддерживаете свежие версии Go (1.18+);
    • вам важна лаконичность и единый код для всех типов;
    • производительности generic-вариантов достаточно.
  • Используйте кодогенерацию, когда:

    • вы хотите сильной оптимизации под конкретные типы;
    • необходимо встроить методы прямо в типы, а не использовать отдельные функции;
    • у вас есть требования к совместимости с более старыми версиями Go;
    • сам по себе сгенерированный код должен быть максимально простым для чтения, без generic-ограничений.

Часто хорошим компромиссом становится подход:

  • проектировать API сначала как generic-пакет;
  • только для «горячих» мест при необходимости включать slice-generator, который делает специализированные версии.

Пример полноценного workflow с slice-generator

Давайте разберемся на примере, который объединит многие идеи из статьи.

Задача:

  • Есть доменный тип User с полем Age.
  • Нужно часто:
    • фильтровать пользователей по возрасту;
    • строить слайс возрастов;
    • разбивать по возрастным группам.

Решение:

  1. Определяем generic-функции Filter, Map, Chunk в общем пакете.
  2. При необходимости создаем кодогенератор, который делает типизированные методы для []User.

Доменный тип

type User struct {
    Name string
    Age  int
}

Использование generic slice-generator

func Adults(users []User) []User {
    // Оставляем только пользователей старше или равных 18 лет
    return Filter(users, func(u User) bool {
        return u.Age >= 18
    })
}

func Ages(users []User) []int {
    // Преобразуем пользователей в их возраст
    return Map(users, func(u User) int {
        return u.Age
    })
}

func AgeGroups(users []User, groupSize int) [][]User {
    // Разбиваем список пользователей на группы по groupSize
    return Chunk(users, groupSize)
}

Здесь slice-generator выражается в виде набора generic-функций, а код получается компактным и читаемым.

Если нужна кодогенерация

Если вы хотите вместо Filter(users, ...) писать users.Filter(...), вы делаете шаблон:

type UserSlice []User

func (s UserSlice) FilterAdults() UserSlice {
    res := make(UserSlice, 0, len(s))
    for _, u := range s {
        if u.Age >= 18 {
            res = append(res, u)
        }
    }
    return res
}

И генерируете похожие методы по шаблону. Это уже «узкоспециализированный» slice-generator поверх доменной модели.


Заключение

Генерация слайсов в Go — это не одна конкретная функция или библиотека, а набор подходов:

  • функции, которые создают и наполняют слайсы по заданным правилам;
  • обобщенные операции Map, Filter, Chunk, построенные поверх слайсов;
  • кодогенерация, когда вы из шаблонов собираете типизированные функции и методы работы со слайсами;
  • утилиты уровня go generate и собственные инструменты slice-generator, которые автоматизируют создание однотипного кода.

Важные моменты, которые стоит держать в голове, когда вы проектируете свои slice-generators:

  • аккуратно работать с len и cap, чтобы минимизировать realLOC;
  • явно решать, копируете ли вы данные или делитесь базовым массивом;
  • продумывать API так, чтобы он был простым: или через функции, или через обертки-структуры;
  • использовать дженерики там, где это возможно, а кодогенерацию — там, где она реально дает выигрыш.

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


Частозадаваемые технические вопросы по теме и ответы

1. Как избежать лишних realLOC при генерации слайсов неизвестной длины

Если длина итогового слайса заранее неизвестна, используйте несколько шагов:

  1. Сначала прикиньте приближенный верхний предел и задайте емкость через make([]T, 0, estimatedCap).
  2. Если оценка сильно неточная, можно:
    • сначала собрать данные в буфер с грубой оценкой;
    • после заполнения сделать res = append([]T(nil), res...), чтобы обрезать лишнюю емкость, если это важно.

Главное — не создавать слайс с очень маленькой емкостью, если точно ожидается большое количество элементов.

2. Как сделать slice-generator, который не аллоцирует лишний раз при каждом вызове

Можно передавать слайс-буфер снаружи и заполнять его:

func GenerateInto(buf []int, n int) []int {
    if cap(buf) < n {
        buf = make([]int, 0, n)
    } else {
        buf = buf[:0]
    }
    for i := 0; i < n; i++ {
        buf = append(buf, i*i)
    }
    return buf
}

Вы повторно используете один и тот же буфер между вызовами, уменьшая количество realLOC.

3. Как правильно организовать несколько шаблонов для разных типов в одном slice-generator

Создайте один шаблон с параметрами TypeName и GoType, а набор конфигураций ([]Config) обойдите циклом, вызывая tmpl.Execute несколько раз в разные файлы. В go generate можно либо:

  • запускать генератор один раз, который создает все файлы;
  • или использовать несколько //go:generate с разными флагами.

Главное — не смешивать разные типы в одном сгенерированном файле, если это затрудняет чтение.

4. Как тестировать сгенерированный код для слайсов

Подход:

  1. Пишите тесты для шаблонов на уровне абстракции: создайте «эталонную» ручную реализацию и сравнивайте поведение.
  2. Для конкретных сгенерированных файлов добавляйте обычные тесты в тех же пакетах.
  3. В CI обязательно запускайте go generate и проверяйте, что репозиторий чист после генерации, чтобы не забывать обновлять код.

5. Как совмещать дженерики и кодогенерацию в одном проекте

Один из рабочих подходов:

  1. Сначала пишете generic-версию функций работы со слайсами.
  2. Для узкого набора «горячих» типов делаете небольшой генератор, который вызывает generic-функции из сгенерированных оберток или инлайнит логику.
  3. В доменном коде используете типизированные обертки, а в менее критичных местах — generic-функции напрямую.
Стрелочка влевоКонфигурация Vite - vite configНастройка линтеров linter-config от основ к рабочему проектуСтрелочка вправо

Все гайды по Feature-sliced_design

Открыть базу знаний

Отправить комментарий