🧬 Generics: Как перестать писать Java на Go Мы ждали их 10 лет.
Мы ждали их 10 лет. И вот, когда они появились, код-ревью превратились в выставку угловых скобок. Я видел разработчиков, которые пытались впихнуть дженерики даже в хендлеры HTTP-запросов.
Коллеги, давайте договоримся на берегу: Generics созданы для работы с типами, а не с поведением. Если вам нужно поведение, у нас уже есть интерфейсы.
Давайте разберем, где дженерики это пушка, а где - технический долг.
❌ Как делать НЕ надо (Бизнес-логика)
Типичная ошибка новичка - пытаться объединить несовместимое через any или огромные union типы, просто чтобы сэкономить пару строк.
// 🤡 Ужасно: Пытаемся сделать "универсальное" сохранение
func SaveToDB[T User | Order | Invoice](db *sql.DB, entity T) error {
// И тут начинается ад из switch type или рефлексии
}
Почему это плохо? Потому что функция все равно должна знать детали каждой структуры, чтобы написать SQL-запрос. Дженерики тут не дают никакой пользы, только усложняют сигнатуру.
Лечение: Используйте интерфейс Saver с методом Save().
✅ Как надо (Структуры данных и Алгоритмы)
Дженерики сияют там, где логике абсолютно плевать, какие данные внутри. Это коллекции (Set, Tree, Queue) и утилитарные функции (Filter, Map).
// 🔥 Отлично: Функция фильтрации слайса
func Filter[T any](slice []T, predicate func(T) bool) []T {
var result []T
for _, v := range slice {
if predicate(v) {
result = append(result, v)
}
}
return result
}
// Использование:
// evens := Filter([]int{1, 2, 3, 4}, func(v int) bool { return v%2 == 0 })
Мы написали это один раз, и оно работает с int, string, User и чем угодно. Больше никаких interface{} и кастов с паниками!
☝️ Нюанс для Senior-ов (Под капотом):
Многие думают, что дженерики в Go работают как шаблоны в C++ (создают копию функции для каждого типа) или как в Java (Type Erasure).
Go пошел своим путем: GCShape (Garbage Collection Shape).
Компилятор группирует типы с одинаковым размером памяти и расположением указателей. Например, все указатели (*User, *Order) имеют одинаковый GCShape, и для них сгенерируется *только одна* версия функции под капотом, куда передастся невидимый словарь с метаданными. А вот для int и float64 сгенерируются разные версии.
Итог: бинарник пухнет не так сильно, как в C++, а скорость работы почти как у обычных функций.
А вы перешли на пакет samber/lo (Lodash для Go на дженериках) или по старинке пишете for руками?
#golang #generics #cleancode #bestpractices
👉 @golang_lib