Библиотека Go (Golang) разработчика
رفتن به کانال در Telegram
Полезные материалы по всему, что может быть полезно Golang разработчику. По всем вопросам @evgenycarter
نمایش بیشتر2 702
مشترکین
اطلاعاتی وجود ندارد24 ساعت
+27 روز
+730 روز
آرشیو پست ها
🔍Тестовое собеседование с Go Senior из Uzum в этот четверг
11 июня(в четверг!) в 19:00 по мск приходи онлайн на открытое собеседование, чтобы посмотреть на настоящее интервью на Middle Go-разработчика.
Как это будет:
📂 Маруф Караев, Senior из Uzum, ex-Яндекс, ex-EPAM будет задавать реальные вопросы и задачи разработчику-добровольцу
📂 Маруф будет комментировать каждый ответ респондента, чтобы дать понять, чего от вас ожидает собеседующий на интервью
📂 В конце можно будет задать любой вопрос Маруфу
Это бесплатно. Эфир проходит в рамках менторской программы от ШОРТКАТ для Go-разработчиков, которые хотят повысить свой грейд, ЗП и прокачать скиллы.
Переходи в нашего бота, чтобы получить ссылку на эфир → @shortcut_go_bot
Реклама.
О рекламодателе.
🔄 Идемпотентность: Как не списать деньги дважды при ретраях
Худшее, что может сделать ваш микросервис, это упасть с пятисоткой. Нет, вру. Худшее, это тихо выполнить операцию дважды.
Все мы знаем, что сеть ненадежна. Поэтому мы оборачиваем HTTP-клиенты в ретраи (например, через
hashicorp/go-retryablehttp). Но тут возникает классическая ловушка распределенных систем: Таймаут не означает, что запрос не выполнился.
Представьте: клиент отправляет запрос "Списать 1000 рублей". Сервис списывает деньги, отправляет ответ 200 OK, но по пути сеть моргнула, и клиент отвалился по таймауту. Клиент делает ретрай. Сервис списывает еще 1000 рублей. Поздравляю, вы в новостях.
Решение: Идемпотентность
Операция называется идемпотентной, если многократное её выполнение дает тот же результат, что и однократное.
Для GET или PUT запросов это работает из коробки. Для POST (создание ресурса, списание) нам нужен Idempotency-Key.
Как это работает на практике:
1. Мобилка (или вызывающий сервис) генерирует уникальный ID для конкретной бизнес-операции (например, UUID v4) и передает его в заголовке Idempotency-Key.
2. Наш Go-сервис перед началом работы проверяет этот ключ.
❌ Как делать НЕ надо (Redis-ловушка):
Многие новички сразу тянутся за Redis: записывают ключ туда, ставят TTL. Но что если Redis недоступен? Или ключи вытеснились из-за нехватки памяти (eviction)? Ваша консистентность рассыпается.
✅ Как надо (Атомарность БД):
Идемпотентность должна жить там же, где и состояние (state) операции — в вашей реляционной базе.
Используйте Unique Constraint в PostgreSQL:
func (s *Service) Charge(ctx context.Context, userID int, amount float64, idempotencyKey string) error {
// Пытаемся сохранить ключ в таблицу idempotency_keys
// Таблица имеет UNIQUE индекс по полю key
_, err := s.db.ExecContext(ctx, `
INSERT INTO idempotency_keys (key, status)
VALUES ($1, 'processing')
ON CONFLICT (key) DO NOTHING
`, idempotencyKey)
// Если ни одной строки не затронуто, значит ключ уже есть!
// Мы можем вернуть старый результат (или ошибку "Уже в обработке")
if err == nil && sqlResult.RowsAffected() == 0 {
return ErrAlreadyProcessed
}
// ... здесь выполняем списание ...
// Обновляем статус ключа на 'done' (в рамках транзакции списания)
return nil
}
🔥 Нюансы для Senior-ов:
• Срок жизни: Ключи идемпотентности не должны жить вечно. Обычно хватает 24-48 часов. Настройте фоновую джобу или партиционирование таблиц (Table Partitioning в PG) для старых ключей, чтобы таблица не пухла до терабайтов.
• Гонка ретраев (Thundering Herd): Если первый запрос завис, а клиент тут же послал второй (ретрай), второй запрос должен получить статус processing и подождать (или отвалиться с 409 Conflict), а не начинать работу параллельно.
Доверяйте базе данных больше, чем сети.
#golang #architecture #microservices #bestpractices #systemdesign
📲 Мы в MAX
👉 @golang_lib⚙️ Лучшие практики в Go: кейсы, которые особенно ждём
С 1 июня у нас стартует новый сезон Podlodka Go Crew — «Лучшие практики в Go». Он пройдёт при поддержке 2ГИС. Будем говорить о том, как писать код, который нормально живёт в продакшене при росте нагрузки и команды.
Вот какие сессии в этом сезоне мы ждём с особенным вниманием:
🚀 «Практика Go оптимизаций: растем вместе с нагрузкой», Алексей Акулович — путь сервиса от прототипа до миллионов RPS, оптимизация CPU, grpc, protobuf и даже собственный GC поверх гошного.
🏗 «Эволюция структуры Go-проекта: как 30 человек пушат в один репозиторий», Кирилл Возжеников — про рост продуктового монорепозитория, построение системы и практики, которые помогают не утонуть в хаосе.
🧩 «Как и зачем писать свой CDC на Go», Юра Саргсян — о ситуации, где стандартных решений уже недостаточно. Postgres, Kafka, гарантии доставки и и подводные камни логической репликации.
🔥 А в конце сезона проведём битву кейсов «50 оттенков межсервисного взаимодействия» — вместе разберём архитектурные задачи с метриками, схемами и ограничением на уточняющие вопросы.
👨💻 Будет много живых инженерных кейсов и обсуждений без абстракций. Приходите — будем разбирать реальные задачи и подходы, которые помогают строить надёжные системы.
🎟 Билеты здесь: https://podlodka.io/gocrew
📦
go mod: Хватит удалять go.sum, когда что-то сломалось
Управление зависимостями в Go выглядит элегантно: написал import, сделал go mod tidy, и всё работает. Но стоит случиться конфликту версий, как многие разработчики переходят в режим паники: удаляют go.mod, удаляют go.sum, чистят кэш и надеются на чудо.
Давайте разберем, как это работает под капотом, чтобы перестать воевать с тулчейном.
1. Миф про go.sum (Это не lock-файл!)
Выходцы из JS (NPM) или Python (Poetry) часто думают, что go.sum - это аналог package-lock.json. Это фатальная ошибка. В Go версия зависимости фиксируется строго в go.mod.
А что тогда делает go.sum? Это криптографическая база данных.
Когда вы скачиваете пакет впервые, Go считает хэш от его исходников и записывает в go.sum. При следующих сборках (например, в CI/CD) Go проверяет, совпадает ли скачанный код с этим хэшем.
Если хэш не совпадает, тулчейн бьет тревогу. Это защита от атаки Supply Chain (когда злоумышленник взломал GitHub-репозиторий популярной библиотеки и подменил там код, не меняя версию).
Вывод: Удаляя go.sum при любой непонятной ошибке, вы отключаете встроенный антивирус языка. Если хэш не сошелся - сначала выясните, почему, а не сносите файл.
2. Директива replace - заряженный пистолет
В go.mod есть мощная фича: replace. Она позволяет подменить одну зависимость на другую (или на локальную папку).
✅ Идеальный юзкейс: Вы разрабатываете два микросервиса локально. Сервис А зависит от Сервиса Б. Чтобы не пушить изменения Б в гит ради каждой проверки, вы пишете:
replace github.com/myorg/serviceB => ../serviceB
❌ Катастрофа: Вы забываете убрать эту строчку и мержите в main.
Если ваш код - это библиотека, которую скачают другие, их тулчейн проигнорирует ваш replace (он работает только в главном модуле). В итоге у них всё сломается. Если это финальный бинарник - CI/CD упадет, потому что пути ../serviceB на сборочном сервере не существует.
3. Проклятие GOPROXY
По умолчанию Go скачивает модули не напрямую с GitHub, а через прокси-сервер Google (proxy.golang.org).
Если вы работаете в энтерпрайзе с приватными репозиториями (GitLab, Bitbucket), go mod tidy вернет ошибку 404 или 410, потому что гугловский прокси не имеет доступа к вашему внутреннему коду.
Senior Tip: Настройте GOPRIVATE
Обязательно укажите тулчейну, какие домены нельзя искать в публичном прокси:
go env -w GOPRIVATE=github.com/my-secret-org/*,gitlab.company.local/*
Это спасет кучу нервов и предотвратит утечку имен ваших приватных модулей на публичные сервера.
#golang #gomod #architecture #devops #bestpractices
📲 Мы в MAX
👉 @golang_lib👣 Хотите вкатиться в разработку с нуля? Рассмотрите Golang, изучите одну из важных тем языка
📚 На открытом уроке разберём, как устроена типизация в Go, как работают указатели и где хранятся данные — стек, куча или статическая память. Покажем на примерах, как язык управляет памятью и почему это влияет на производительность.
Урок проходит в преддверии старта курса «Go-разработчик. Базовый уровень». Если вы хотите разобраться в базовых механизмах языка и писать код без скрытых ошибок — подключайтесь
🤓Встречаемся 21 мая в 20:00 МСК. Регистрация открыта: https://vk.cc/cXMZhQ
Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576, www.otus.ru
🗑 Сборщик мусора в Go: Скрытый налог на ваш CPU
Если вы спросите джуна, как работает память в Go, он ответит: "Ну, там есть GC, он сам всё чистит".
Сеньор же знает: GC - это не волшебная фея, это строгий налоговый инспектор. Он может не забирать ваши деньги (память), но он заберет ваше время (CPU).
Давайте развеем главные мифы и заглянем под капот сборщика мусора в Go.
Миф 1: GC вызывает долгие паузы (Stop-The-World)
Выходцы из старой Java (или те, кто писал на Go до версии 1.5) до сих пор пугают детей долгими паузами, когда приложение буквально замирает на секунды.
В современном Go это не так. Наш GC - это Concurrent Mark and Sweep (Конкурентная пометка и очистка). Фаза Stop-The-World (когда тормозится вообще всё) всё ещё есть, но она занимает доли миллисекунды.
Как он работает (Трехцветный алгоритм):
Представьте, что GC - это маляр, который красит ваши объекты:
1. Белые - объекты-кандидаты на удаление (изначально все такие).
2. Серые - объекты, до которых мы смогли дотянуться из корней (глобальные переменные, стеки горутин), но мы еще не проверили, на что ссылаются они сами.
3. Черные - живые объекты, которые мы проверили полностью.
GC бегает по ссылкам, превращая серые объекты в черные, а новые найденные - в серые. Когда серых не остается, все оставшиеся белые объекты просто стираются из памяти.
И главное - он делает это параллельно с работой вашего кода!
В чем подвох? (Mark Assist)
Если паузы такие короткие, почему мы вообще боремся за zero-allocation код?
Потому что чудес не бывает. GC работает в фоне и забирает под себя до 25% CPU (четверть ваших потоков
P).
Но это еще не всё. Если ваше приложение генерирует мусор (белые объекты) быстрее, чем фоновый GC успевает их красить, планировщик включает режим паники - Mark Assist.
Он буквально берет вашу горутину, которая обрабатывает важный HTTP-запрос пользователя, и говорит: "Слышь, прежде чем я дам тебе память, иди-ка помоги мне покрасить вон те объекты".
В итоге ваш запрос, который обычно отрабатывает за 5мс, внезапно зависает на 50мс. Вы смотрите в логи и ничего не понимаете.
🔥 Senior Tip: Как этим управлять?
В Go почти нет ручек для тюнинга GC, но есть две самые важные переменные окружения:
1. GOGC (по умолчанию 100). Означает "запускать GC, когда куча выросла на 100% от предыдущего размера". Если у вас куча свободной RAM, ставьте GOGC=500. Мусор будет копиться дольше, GC будет запускаться реже, CPU скажет спасибо.
2. GOMEMLIMIT (появилось в Go 1.19). Это "мягкий" лимит памяти. Позволяет сказать GC: "Не запускайся часто, пока мы не упремся в 2 ГБ, а вот если подошли к лимиту - чисти агрессивно". Это спасение от OOM (Out Of Memory) в Kubernetes.
Мораль: Не бойтесь GC, но уважайте его труд. Используйте sync.Pool, заранее аллоцируйте слайсы (make([]int, 0, capacity)) и не передавайте огромные структуры по значению там, где это не нужно.
А вы уже ставили GOMEMLIMIT на проде или до сих пор ловите случайные рестарты подов по OOM? Делитесь в комментах 👇
#golang #underhood #performance #memory #architecture
📲 Мы в MAX
👉 @golang_lib🧬 Generics: Как перестать писать Java на Go
Мы ждали их 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
📲 Мы в MAX
👉 @golang_libRepost from AvitoTech
Коллеги, принесли 🪑🪑🪑 и 🍿🍿🍿
Потому что Райан Гослинг не каждый день про техдолг продаёт!
«Нам нужно заняться техдолгом» — сказал тимлид.
«А можно как-то быстрее?» — ответил бизнес.
И вот вы уже живёте в мире, где:
🔥 система «почти не падает» (и это считается успехом);
🔥 один модуль лучше не трогать вообще;
🔥 хотфиксы делаются «на вечер»;
🔥 а разработчику теперь требуется психолог.
Как же так получается и почему «оно же работает» ≠ стабильность. И да, объяснили, откуда берутся эти магические «две недели вместо трёх месяцев». Приятного просмотра!
📺 YouTube
🔵 ВК Видео
#tl #backend #frontend #go
🧩 Struct Padding: Как вы теряете гигабайты памяти на ровном месте
Знаете это чувство, когда вы долго проектируете структуру, высчитываете типы, используете
int8 вместо int, чтобы сэкономить память... а потом смотрите в профайлер и плачете?
Вы думаете, что экономите память, а компилятор тихо посмеивается и подкидывает вам мусорные байты.
Проблема в том, что железо читает память не побайтово, а "машинными словами" (обычно по 8 байт на 64-битных архитектурах). Чтобы CPU было удобно и быстро читать данные, компилятор Go выравнивает переменные в памяти, вставляя между ними пустые байты - Padding.
Давайте смотреть на код.
❌ Плохо (Дуршлаг из паддинга):
type BadStruct struct {
a bool // 1 байт
b int64 // 8 байт
c bool // 1 байт
}
Кажется, что размер структуры 1 + 8 + 1 = 10 байт?
По факту: вызов unsafe.Sizeof(BadStruct{}) вернет 24 байта!
Компилятор видит a (1 байт), понимает, что следующий b (8 байт) должен лежать на границе 8 байт, и вставляет 7 байт пустоты. Затем кладет c, и добивает конец структуры еще 7 байтами до ровного числа.
✅ Хорошо (Сортировка от большего к меньшему):
type GoodStruct struct {
b int64 // 8 байт
a bool // 1 байт
c bool // 1 байт
}
Теперь unsafe.Sizeof(GoodStruct{}) равен 16 байтам. Мы сэкономили 33% памяти, просто поменяв строчки местами! А если у вас в in-memory кэше лежат миллионы таких объектов? Это гигабайты сэкономленной RAM на ровном месте.
☝️ Нюансы для Senior-ов:
🩵Не оптимизируйте всё подряд. Если структура создается один раз при старте приложения (например, Config) - забейте. Читаемость и логическая группировка полей важнее. Оптимизируйте только то, что массово аллоцируется, живет в больших слайсах или летает по сети.
🩵Автоматизация. Вам не нужно считать байты в уме. Включите линтер fieldalignment (он есть в составе go vet или golangci-lint). Он сам укажет на проблему сообщением вроде: "struct with 24 pointer bytes could be 16".
#golang #performance #memory #optimization #underhood
📲 Мы в MAX
👉 @golang_lib🚀 Подборка полезных IT каналов в Max
Системное администрирование, DevOps 📌
https://max.ru/i_odmin Все для системного администратора
https://max.ru/bash_srv Bash Советы
https://max.ru/sysadminof Книги для админов, полезные материалы
https://max.ru/i_odmin_book Библиотека Системного Администратора
https://max.ru/i_devops DevOps: Пишем о Docker, Kubernetes и др.
https://max.ru/tipsysdmin Типичный Сисадмин
1C разработка 📌
https://max.ru/odin1c_rus Cтатьи, курсы, советы, шаблоны кода 1С
Программирование C++📌
https://max.ru/cpp_lib Библиотека C/C++ разработчика
Программирование Go📌
https://max.ru/golang_lib Библиотека Go (Golang) разработчика
Программирование React📌
https://max.ru/react_lib React
Программирование Python 📌
https://max.ru/python_of Python академия.
https://max.ru/BookPython Библиотека Python разработчика
Java разработка 📌
https://max.ru/bookjava Библиотека Java разработчика
GitHub Сообщество 📌
https://max.ru/githublib Интересное из GitHub
Базы данных (Data Base) 📌
https://max.ru/database_info Все про базы данных
Фронтенд разработка 📌
https://max.ru/frontend_1 Подборки для frontend разработчиков
Библиотеки 📌
https://max.ru/programmist_of Книги по программированию
https://max.ru/proglb Библиотека программиста
https://max.ru/bfbook Книги для программистов
Программирование 📌
https://max.ru/bookflow Лекции, видеоуроки, доклады с IT конференций
https://max.ru/itmozg Программисты, дизайнеры, новости из мира IT
https://max.ru/php_lib Библиотека PHP программиста 👨🏼💻👩💻
Шутки программистов 📌
https://max.ru/itumor Шутки программистов
Защита, взлом, безопасность 📌
https://max.ru/thehaking Канал о кибербезопасности
https://max.ru/xakkep_1 Хакер Free
Книги, статьи для дизайнеров 📌
https://max.ru/odesigners Статьи, книги для дизайнеров
Математика 📌
https://max.ru/Pomatematike Канал по математике
https://max.ru/phismat_1 Обучающие видео, книги по Физике и Математике
Вакансии 📌
https://max.ru/progjob Вакансии в IT
Мир технологий 📌
https://max.ru/mir_teh Канал для любознательных
Бонус 📌
https://max.ru/piterspb_78 Свежие новости Санкт-Петербурга
https://max.ru/mockva_life Свежие новости Москвы
🚀 PGO: Как получить +10% к скорости, не написав ни строчки кода
Все мы любим оптимизировать. Переписываем мапы, пулим объекты в
sync.Pool, боремся с аллокациями. Но что, если я скажу, что в новых версиях Go (начиная с 1.21) можно ускорить приложение на 5-10%, просто подкинув компилятору один файлик?
Profile-Guided Optimization (PGO).
В чем проблема обычного компилятора?
При стандартной сборке компилятор опирается на эвристики. Он смотрит на функцию и гадает: "Наверное, эту функцию вызывают часто, давай-ка я её заинлайню (inline), чтобы сэкономить на вызове". Но компилятор не знает, как ваш код ведет себя в реальном продакшене.
Что меняет PGO?
PGO ломает этот слепой подход. Вы берете профиль нагрузки (CPU profile) с реально работающего продакшена и отдаете его компилятору при сборке следующего релиза.
Компилятор смотрит в профиль: "Ага, вот эта функция processOrder жрет 30% CPU, инлайним её агрессивно! А эта handleError вызывается раз в год - убираем её с горячего пути, чтобы не засорять кэш процессора".
Как это сделать (3 простых шага):
1. Собираем профиль с прода. Идем на боевой (или нагрузочный) сервер, где подключен net/http/pprof, и стягиваем 30-секундный профиль:
curl -o default.pgo http://prod-server:8080/debug/pprof/profile?seconds=30
2. Кладем файл в корень проекта. Просто кидаете файл default.pgo в главную директорию вашего модуля (там же, где лежит go.mod).
3. Собираем как обычно.
go build -o myapp
Всё. Начиная с Go 1.21.2, флаг -pgo=auto включен по умолчанию. Компилятор сам найдет файл default.pgo и оптимизирует бинарник.
☝️ Нюансы для Senior-ов:
• А что если исходный код изменился? PGO в Go спроектирован устойчивым к изменениям (robust). Если вы собрали профиль, а потом немного порефакторили код, компилятор не сойдет с ума. Он применит оптимизации там, где функции совпали, и безопасно проигнорирует несовпадения.
• Где брать профиль для CI/CD?
Настройте автоматический сбор профиля с продакшена (например, раз в неделю) и коммитьте его прямо в репозиторий. Да, бинарный файл в гите - звучит как ересь, но для PGO это официальная рекомендация от команды Go.
#golang #performance #pgo #optimization
📲 Мы в MAX
👉 @golang_lib🌪 Fuzzing: Ломаем свой код, пока это не сделали другие
Мы привыкли писать Unit-тесты по принципу: "Я ожидаю, что если подать А, выйдет Б".
Но проблема Unit-тестов в том, что они ограничены вашей фантазией. Вы тестируете только те кейсы, которые смогли придумать.
А что будет, если подать пустую строку? А если строку из 10 МБ эмодзи? А если битый JSON?
Тут на сцену выходит Fuzzing (Фаззинг).
Начиная с Go 1.18, он встроен прямо в
go test.
Как это работает?
Фаззер - это бесконечная обезьяна, которая лупит по клавиатуре, пытаясь сломать вашу функцию. Но обезьяна умная.
1. Она берет ваши примеры (seed corpus).
2. Немного их меняет (мутирует биты, обрезает, дублирует).
3. Следит за покрытием кода (code coverage).
4. Если новый ввод зашел в новую ветку if, фаззер запоминает этот ввод и начинает мутировать уже его.
Цель фаззера - найти такие данные, которые вызовут panic, бесконечный цикл или съедят всю память.
Пример:
Допустим, у нас есть "безопасная" функция деления:
func SafeDiv(a, b int) int {
if b == 0 {
return 0 // Защита от паники?
}
return a / b
}
Пишем фазз-тест:
func FuzzSafeDiv(f *testing.F) {
// 1. Seed Corpus: даем примеры валидных данных
f.Add(10, 2)
f.Add(100, 5)
// 2. Fuzz Loop: запускаем хаос
f.Fuzz(func(t *testing.T, a int, b int) {
// Вызываем нашу функцию
// Если она запаникует — тест упадет
SafeDiv(a, b)
})
}
Запускаем: go test -fuzz=Fuzz
И через секунду фаззер найдет баг, о котором забывают 90% джунов (и 30% сеньоров).
При вводе SafeDiv(-9223372036854775808, -1) (MinInt64 / -1) программа упадет с переполнением, потому что результат не влезает в int64. Unit-тест вы бы такой в жизни не написали.
🔥 Senior Tip: Property-based Testing
Фаззинг нужен не только, чтобы искать паники. Он идеален для проверки инвариантов.
Например, если вы написали свой кодек или шифрование:
f.Fuzz(func(t *testing.T, original []byte) {
encoded := Encode(original)
decoded := Decode(encoded)
if !bytes.Equal(original, decoded) {
t.Fatalf("Данные повредились: %v -> %v", original, decoded)
}
})
Это найдет баги в пограничных случаях (например, когда байт равен 0x00 или 0xFF).
#golang #testing #fuzzing #security #quality
📲 Мы в MAX
👉 @golang_lib🚀 Подборка полезных IT каналов в Max
Системное администрирование, DevOps 📌
https://max.ru/i_odmin Все для системного администратора
https://max.ru/bash_srv Bash Советы
https://max.ru/sysadminof Книги для админов, полезные материалы
https://max.ru/i_odmin_book Библиотека Системного Администратора
https://max.ru/i_devops DevOps: Пишем о Docker, Kubernetes и др.
https://max.ru/tipsysdmin Типичный Сисадмин
1C разработка 📌
https://max.ru/odin1c_rus Cтатьи, курсы, советы, шаблоны кода 1С
Программирование C++📌
https://max.ru/cpp_lib Библиотека C/C++ разработчика
Программирование Go📌
https://max.ru/golang_lib Библиотека Go (Golang) разработчика
Программирование React📌
https://max.ru/react_lib React
Программирование Python 📌
https://max.ru/python_of Python академия.
https://max.ru/BookPython Библиотека Python разработчика
Java разработка 📌
https://max.ru/bookjava Библиотека Java разработчика
GitHub Сообщество 📌
https://max.ru/githublib Интересное из GitHub
Базы данных (Data Base) 📌
https://max.ru/database_info Все про базы данных
Фронтенд разработка 📌
https://max.ru/frontend_1 Подборки для frontend разработчиков
Библиотеки 📌
https://max.ru/programmist_of Книги по программированию
https://max.ru/proglb Библиотека программиста
https://max.ru/bfbook Книги для программистов
Программирование 📌
https://max.ru/bookflow Лекции, видеоуроки, доклады с IT конференций
https://max.ru/itmozg Программисты, дизайнеры, новости из мира IT
https://max.ru/php_lib Библиотека PHP программиста 👨🏼💻👩💻
Шутки программистов 📌
https://max.ru/itumor Шутки программистов
Защита, взлом, безопасность 📌
https://max.ru/thehaking Канал о кибербезопасности
https://max.ru/xakkep_1 Хакер Free
Книги, статьи для дизайнеров 📌
https://max.ru/odesigners Статьи, книги для дизайнеров
Математика 📌
https://max.ru/Pomatematike Канал по математике
https://max.ru/phismat_1 Обучающие видео, книги по Физике и Математике
Вакансии 📌
https://max.ru/progjob Вакансии в IT
Мир технологий 📌
https://max.ru/mir_teh Канал для любознательных
Бонус 📌
https://max.ru/piterspb_78 Свежие новости Санкт-Петербурга
https://max.ru/mockva_life Свежие новости Москвы
Челлендж по обработке миллиарда строк на Go: от 1 минуты 45 секунд до 4 секунд
Пару недель назад я прочитал о запавшем мне в душу челлендже по обработке миллиарда строк, поэтому захотел решить его на Go.
Я немного опоздал, соревнования проводились в январе. И на Java. Меня не особо интересует Java, зато давно интересует оптимизация кода на Go.
Этот челлендж был очень прост: обработать текстовый файл названий метеорологических станций и температур, и для каждой станции вывести минимальное, среднее и максимальное значение. Чтобы упростить задачу, было ещё несколько ограничений, однако я проигнорировал те, что относятся только к Java.
https://habr.com/ru/articles/798215/
📲 Мы в MAX
👉 @golang_lib
🧪 Table-Driven Tests: Хватит плодить функции-клоны
Проверяю PR джуниора. Файл на 500 строк, из них 450 - это тесты. Смотрю внимательнее, а там
Test_ValidateEmail_Empty, Test_ValidateEmail_NoAt, Test_ValidateEmail_Valid... Каждая функция копирует предыдущую на 90%. Любое изменение сигнатуры ValidateEmail заставит переписывать весь этот ад руками.
Коллеги, в Go так не делают. Наш путь - Table-Driven Tests.
Идея гениально проста: мы отделяем данные от логики тестирования. Логика пишется один раз, а тест-кейсы складываются в компактную таблицу (обычно это слайс анонимных структур).
Как это выглядит на практике:
func TestDivide(t *testing.T) {
// 1. Наша "таблица" тест-кейсов
tests := []struct {
name string // Имя кейса (обязательно)
a, b float64 // Входящие аргументы
want float64 // Ожидаемый результат
wantErr bool // Ожидаем ли ошибку?
}{
{"normal division", 10, 2, 5, false},
{"divide by zero", 10, 0, 0, true},
{"fractional result", 5, 2, 2.5, false},
}
// 2. Единая логика прогона
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := Divide(tt.a, tt.b)
if (err != nil) != tt.wantErr {
t.Fatalf("Divide() error = %v, wantErr %v", err, tt.wantErr)
}
if got != tt.want {
t.Errorf("Divide() got = %v, want %v", got, tt.want)
}
})
}
}
1. DRY (Don't Repeat Yourself). Логика вызова и проверок написана один раз.
2. Читаемость. Таблица читается как документация к функции. Сразу видно все граничные условия.
3. Легкость расширения. Добавить новый кейс - это добавить одну строчку в структуру, а не писать новую функцию на 15 строк.
🔥 Senior Tips:
• Используйте Map вместо Slice. Замените []struct{...} на map[string]struct{...}, где ключ мапы - это name теста. Почему? Итерация по мапам в Go рандомизирована. Это значит, что при каждом запуске ваши тест-кейсы будут выполняться в разном порядке. Это отличный бесплатный способ убедиться, что тесты не зависят друг от друга (не шарят глобальное состояние)!
• t.Parallel() для скорости.
Если в цикле выполняются тяжелые тесты (например, интеграционные с БД), добавьте t.Parallel() первой строкой внутри t.Run. (Только помните про замыкание переменных цикла, если сидите на Go < 1.22!).
Пишите тесты так, чтобы их хотелось читать, а не прокручивать с закрытыми глазами.
#golang #testing #cleancode #bestpractices
📲 Мы в MAX
👉 @golang_libContext Cancellation - это не
kill -9
Одна из самых частых иллюзий у разработчиков, приходящих в Go:
"Я вызвал cancel(), почему моя горутина всё еще работает?"
Важно запомнить: В Go нельзя принудительно убить горутину снаружи. Нет никакого PID, по которому можно стрельнуть сигналом.
context в Go реализует кооперативную отмену.
Это значит, что cancel() - это не приказ "Умри!", а вежливое сообщение: "Брат, ты нам больше не нужен, сворачивайся, как сможешь".
Если ваша горутина не проверяет этот канал, она будет молотить до победного конца (или до паники), даже если клиент давно отвалился по таймауту.
❌ Как делать НЕ надо (Игнорирование)
func HeavyWork(ctx context.Context) {
// Мы передали контекст, но... не используем его
for i := 0; i < 1000000; i++ {
calculateHash(i) // Жжёт CPU впустую, если ctx отменен
}
}
✅ Как надо (Проверка канала)
В любом долгом цикле или блокирующей операции вы обязаны слушать ctx.Done().
func HeavyWork(ctx context.Context) error {
for i := 0; i < 1000000; i++ {
// Вариант 1: Неблокирующая проверка в каждой итерации
select {
case <-ctx.Done():
return ctx.Err() // "context canceled" или "deadline exceeded"
default:
// Работаем дальше
}
calculateHash(i)
}
return nil
}
Нюанс для Сеньоров: context.Cause (Go 1.20+)
Раньше, получая ctx.Err(), мы видели просто context canceled. Это неинформативно. Кто отменил? Почему?
Начиная с Go 1.20, используйте WithCancelCause:
ctx, cancel := context.WithCancelCause(parent)
// Где-то в логике отмены:
cancel(fmt.Errorf("client disconnect inside handler"))
// В горутине:
if ctx.Err() != nil {
fmt.Println(context.Cause(ctx)) // Печатает конкретную причину!
}
☝️Context - это кровеносная система вашего приложения. Если вы пишете функцию, которая делает что-то дольше 10мс или ходит в сеть/базу - всегда принимайте первым аргументом ctx и всегда прокидывайте его дальше. Библиотеки (pgx, net/http) уже умеют его слушать, просто дайте им этот шанс.
Не плодите горутины-зомби. 🧟♂️
#golang #context #concurrency #bestpractices
📲 Мы в MAX
👉 @golang_libДля тех, кто строит и масштабирует сервисы на Go: 25 апреля пройдет Я.Субботник по Go
Яндекс проведет митап офлайн в Санкт-Петербурге и онлайн. Участников будут ждать практические доклады, живые кейсы и решения, проверенные под нагрузкой.
Руководитель группы разработки Городских сервисов Владимир Тельбухов покажет, как наладить консистентность в интеграциях с внешними сервисами. А разработчик Yandex Cloud Иван Похабов, расскажет, как устроен GoBGP, какие баги в нём встречаются и как их исправляют в production. Полную программу выступлений можно найти на сайте.
Помимо докладов, для участников проведут круглые столы, квиз по Go, а также экскурсию по питерскому офису Яндекса, для которой офлайн-участникам потребуется предварительная регистрация.
👉 Регистрируемся
"Семафор". Не убей базу данных
Запустить 10,000 горутин в Go дешево. А вот открыть 10,000 коннектов к базе или внешнему API - дорого и больно.
Если вы просто запустите
go func() в цикле по всему слайсу данных, OOM Killer или таймауты придут мгновенно. Нам нужно ограничить количество одновременно работающих воркеров.
Самый идиоматичный способ в Go - буферизированный канал в роли семафора.
Реализация:
func ProcessItems(items []int) {
maxConcurrency := 5
// Семафор — канал с емкостью = лимиту
sem := make(chan struct{}, maxConcurrency)
var wg sync.WaitGroup
for _, item := range items {
wg.Add(1)
// Блокируемся здесь, если в канале уже 5 токенов
sem <- struct{}{}
go func(val int) {
defer wg.Done()
// Освобождаем слот в семафоре при выходе
defer func() { <-sem }()
// Тяжелая логика тут
process(val)
}(item)
}
wg.Wait()
}
Почему это круто:
1. Backpressure: Мы не создаем миллион горутин, которые висят в sleep. Горутина создается только когда есть свободный слот (точнее, цикл for блокируется на записи в канал).
2. Простота: Никаких внешних библиотек.
Нюанс для профи:
Если порядок обработки не важен - это идеальное решение. Если важен - смотрите в сторону Pipeline паттерна.
#golang #concurrency #semaphore #architecture
📲 Мы в MAX
👉 @golang_libХватит мучить
sync.WaitGroup для HTTP-запросов
Каждый джун проходит этот путь:
1. Запускаем 10 горутин через go func().
2. Добавляем wg.Add(1).
3. Понимаем, что одна горутина может вернуть ошибку.
4. Создаем канал для ошибок, мьютекс или (о ужас) глобальную переменную.
5. Код превращается в нечитаемую простыню.
Коллеги, для групповых задач с возвратом ошибок есть стандартный (почти) инструмент - errgroup.
Пакет golang.org/x/sync/errgroup делает три вещи, которые вы обычно пишете руками:
1. Ждет завершения всех горутин.
2. Возвращает первую случившуюся ошибку.
3. (Опционально) Отменяет контекст для остальных, если кто-то один упал.
Как это выглядит:
import "golang.org/x/sync/errgroup"
func fetchAll(urls []string) error {
// Создаем группу и контекст
g, _ := errgroup.WithContext(context.Background())
for _, url := range urls {
url := url // Go 1.22 fix not needed, but habit 🙂
// g.Go принимает функцию вида func() error
g.Go(func() error {
resp, err := http.Get(url)
if err == nil {
resp.Body.Close()
}
return err // Если вернем err != nil, контекст отменится
})
}
// Ждем всех. Если была ошибка — получим её.
return g.Wait()
}
🔥 Senior Tip:
В последних версиях добавили метод g.SetLimit(n).
Это киллер-фича. Она превращает errgroup в Worker Pool с ограничением конкурентности. Больше не нужно создавать семафоры на каналах, чтобы не за-DDOS-ить внешний API. Просто добавьте g.SetLimit(10) перед циклом.
Чисто, лаконично, и никаких дедлоков на wg.Done().
#golang #concurrency #patterns #bestpractices
📲 Мы в MAX
👉 @golang_lib
اکنون در دسترس! پژوهش تلگرام ۲۰۲۵ — مهمترین بینشهای سال 
