Библиотека Go-разработчика | Golang
24K subscribers
2.63K photos
48 videos
88 files
5.18K links
Все самое полезное для Go-разработчика в одном канале.

По рекламе: @proglib_adv

Учиться у нас: https://proglib.io/w/32d20779

Для обратной связи: @proglibrary_feeedback_bot

РКН: https://gosuslugi.ru/snet/67a4a8c24689c2151c752af0

#WXSSA
Download Telegram
🔥 База по экономике токенов и кэшированию от AI Platform Lead из Bitrix24

Знакомьтесь, Сергей Нотевский. AI Platform Lead в Bitrix24.

Он один из ключевых экспертов нашего курса AgentOps. На своих лекциях он детально разбирает экономику AI-агентов, кэширование токенов, LLM-инфраструктуру и вывод генеративных систем в стабильный прод.

Мы попросили Сергея поделиться материалами для тех, кто хочет оптимизировать косты на LLM в проде. Сохраняйте методичку по prefix cache метрике, которая напрямую влияет на ваши деньги.

Как говорят создатели Manus:
“KV-cache hit rate is the single most important metric for a production-stage AI agent.”


🛠 Что внутри методички (комбо из 3 статей + код):
Экономика кэширования — особенности провайдеров и как правильно считать затраты.

Частые анти-паттерны — почему ваш кэш постоянно сбрасывается и вы платите больше.

Кэш в AI-агентах — специфика работы с памятью в автономных системах.


🍒 Вишенка на торте: готовый SKILL для агента, который делает ревью вашего проекта, находит анти-паттерны и предотвращает низкое попадание в кэш.

Забрать комбо-материалы на GitHub

P.S. Если хотите послушать Сергея вживую — ловите его на конференциях Kode Waves (май), Conversations AI и Highload Spb (июнь).

🎁 Акция в честь старта продаж!

Прямо сейчас при покупке Инженерного трека вы получаете полный доступ к материалам курса «Разработка ИИ-агентов» в подарок.

👉 Забрать 2 курса по цене 1 и начать обучение
😉 Тесты для конкурентного кода

Каждый, кто писал тесты для конкурентного Go, знает эту боль. Нужно проверить, что что-то произошло (или не произошло) после работы горутины. А единственный способ это сделать — воткнуть time.Sleep и надеяться, что на CI этого хватит.

Выглядело это примерно так:
func TestTimeout(t *testing.T) {
// ... настраиваем логику таймаута ...

time.Sleep(10 * time.Millisecond) // авось хватит

if !timedOut {
t.Fatal("expected timeout, got none")
}
}


10 миллисекунд на один тест — мелочь. Но когда таких тестов сотни, набегают секунды пустого ожидания. А на нагруженном CI раннере 10ms иногда недостаточно, и тест становится flaky.

Что делает synctest

Пакет testing/synctest появился экспериментально в Go 1.24 за флагом GOEXPERIMENT=synctest. В Go 1.25 он вышел в GA с обновлённым API — флаг больше не нужен. Вместо старого synctest.Run используем synctest.Test. Старый API удалён в Go 1.26.

Пакет решает проблему двумя механизмами.

Bubble. synctest.Test() оборачивает тест в изолированную среду. Все горутины, запущенные внутри, принадлежат этому «пузырю».

Виртуальное время. Внутри пузыря time.Sleep, таймеры и тикеры работают на фейковых часах. Реальное время не идёт. Часы сдвигаются только тогда, когда все горутины в пузыре заблокированы и ждут. Тест с пятисекундным таймаутом выполняется за микросекунды.

`synctest.Wait()` блокирует выполнение, пока все остальные горутины в пузыре не окажутся в состоянии устойчивой блокировки. После возврата из Wait() мы точно знаем, что система обработала всё, что могла.

Вот тот же тест, переписанный на synctest:
import "testing/synctest"

func TestTimeout(t *testing.T) {
synctest.Test(t, func(t *testing.T) {
const timeout = 5 * time.Second
ctx, cancel := context.WithTimeout(
context.Background(), timeout,
)
defer cancel()

time.Sleep(timeout - time.Nanosecond)
synctest.Wait()
if err := ctx.Err(); err != nil {
t.Fatalf("context expired early: %v", err)
}

time.Sleep(time.Nanosecond)
synctest.Wait()
if ctx.Err() != context.DeadlineExceeded {
t.Fatalf("expected DeadlineExceeded, got %v", ctx.Err())
}
})
}


Никакого реального ожидания. Никаких flaky. Никаких изменений в тестируемом коде.

Что считается «устойчивой блокировкой»

Не все блокирующие операции равны. synctest.Wait() учитывает только те, которые могут быть разблокированы исключительно горутинами внутри пузыря.

Учитываются: отправка и получение из каналов, созданных внутри пузыря, time.Sleep, sync.Cond.Wait, sync.WaitGroup.Wait.

Не учитываются: sync.Mutex (обычно захватывается ненадолго), сетевые вызовы и чтение файлов (их может разблокировать ядро ОС).

Сетевые тесты

Для тестирования сетевого взаимодействия внутри пузыря используем net.Pipe() — это in-memory соединения, которые synctest считает устойчиво блокирующими:
func TestServerHandshake(t *testing.T) {
synctest.Test(t, func(t *testing.T) {
serverConn, clientConn := net.Pipe()
defer serverConn.Close()
defer clientConn.Close()

go runServer(serverConn)
go runClient(clientConn)
synctest.Wait()
// Все горутины заблокированы — хендшейк завершён
})
}


Запуск

На Go 1.25+ никаких дополнительных флагов:
go test ./...


Если вы до сих пор боретесь с flaky-тестами в конкурентном коде, synctest убирает главную причину — гадание на time.Sleep. Тесты становятся быстрыми и детерминированными.

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека Go-разработчика

#GoToProduction
Please open Telegram to view this post
VIEW IN TELEGRAM
👍102
🥶 Go 1.27 уже в release freeze

Команда Go объявила о начале заморозки релиза Go 1.27. С этого момента новые фичи и оптимизации не принимаются до следующего цикла. Багфиксы по-прежнему можно отправлять.

Release Candidate 1 запланирован на вторую неделю июня. Сейчас основной приоритет — закрытие issues, блокирующих RC 1.

➡️ Источник

У команды Go есть новости, а у нас есть новостная рассылка

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека Go-разработчика

#GoLive
Please open Telegram to view this post
VIEW IN TELEGRAM
5👍1
⚙️Практикуем Go вместе с Podlodka Go Crew

Сервис вроде работает, но любая новая нагрузка быстро вскрывает проблемы: растут задержки, появляются утечки памяти, а временные решения слишком часто закрепляются в архитектуре. Знакомо?

С 1 по 5 июня Podlodka Go Crew совместно с 2ГИС проведут сезон «Лучшие практики в Go», где участники разберут, как делать сервисы, которые нормально работают под нагрузкой и остаются управляемыми.

В программе:


🧩 Паттерны в Go: что работает, а что приходится переосмыслять

⚡️ Рост Go-сервисов под нагрузкой — путь от прототипа до миллионов RPS

🛠 Тестирование: от базовых сценариев до выстраивания пирамиды тестов

🔍 Живой разбор кейсов межсервисного взаимодействия

🤝 Обсуждение влияния AI на процессы разработки с экспертами Go-сообщества

Формат — пять дней живых Zoom-сессий утром и вечером, закрытое сообщество в Telegram и общение со спикерами.

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

👉 Билеты по early-bird цене и программа ждут здесь: https://podlodka.io/gocrew

А по нашему промокоду goproglib получите скидку🎁
👾1
Пакет time в Go: таймеры, тикеры и паттерны

Стандартный time закрывает почти все задачи по работе со временем без сторонних библиотек. Разберём основные примитивы и паттерны.

Таймеры

time.NewTimer() срабатывает один раз через заданный интервал:
timer := time.NewTimer(2 * time.Second)
<-timer.C
fmt.Println("Timer fired!")


Таймер можно остановить до срабатывания через timer.Stop() и перезапустить через timer.Reset().

time.After() — сокращение без доступа к таймеру. time.AfterFunc() вызывает функцию в отдельной горутине.

Тикеры

time.NewTicker() срабатывает повторно через равные интервалы. Всегда вызывайте Stop(), иначе горутина утечёт:
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()

for i := 0; i < 5; i++ {
<-ticker.C
fmt.Println("Tick", i)
}


Таймауты через context

В продакшене таймауты реализуют через context — это позволяет пробросить отмену по всей цепочке вызовов:
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

select {
case result := <-doWork(ctx):
fmt.Println("Result:", result)
case <-ctx.Done():
fmt.Println("Timeout:", ctx.Err())
}


Замер времени выполнения

Простая обёртка через defer. Первый вызов запоминает старт, возвращённое замыкание откладывается и печатает разницу:
func measureTime(name string) func() {
start := time.Now()
return func() {
fmt.Printf("%s took %v\n", name, time.Since(start))
}
}

defer measureTime("main")()


Rate limiter

Простейший вариант на time.Tick() — один запрос в секунду:
limiter := time.Tick(time.Second)
for req := range requests {
<-limiter
process(req)
}


Retry с экспоненциальным backoff

Пауза удваивается после каждой неудачи, но не превышает maxBackoff. В продакшене стоит добавить jitter, чтобы клиенты не нагружали сервер одновременно:
backoff := 100 * time.Millisecond
maxBackoff := 10 * time.Second

for i := 0; i < maxRetries; i++ {
if err := fn(); err == nil {
return nil
}
time.Sleep(backoff)
backoff *= 2
if backoff > maxBackoff {
backoff = maxBackoff
}
}


Если переходите с других языков — запомните референсную дату 1/2 3:4:5 2006 и не забывайте вызывать ticker.Stop().

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека Go-разработчика

#GoDeep
Please open Telegram to view this post
VIEW IN TELEGRAM
5👾1
📊 pprof открывается с Flame Graph по умолчанию

Небольшое изменение в Go 1.26, которое заметно влияет на процесс отладки производительности.

До версии 1.26 команда go tool pprof -http=:8080 открывала top-down tree view. Чтобы переключиться на Flame Graph, нужно было сделать это вручную. На практике многие просто оставались в tree view, потому что оно открывалось первым.

Теперь pprof сразу показывает flame graph. Разница существенная. Flame graph визуально отображает весь стек вызовов: широкие блоки означают медленные функции, высокие столбцы показывают глубокие цепочки вызовов. Tree view содержит ту же информацию, но в виде текстового списка, который требует больше усилий для анализа.

Если в вашей команде регулярно занимаются отладкой производительности, обновите runbook'и и incident playbook'и с учётом нового дефолтного представления.

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека Go-разработчика

#GoToProduction
Please open Telegram to view this post
VIEW IN TELEGRAM
1👍7
✏️ Мнения разделились

Что выведет:
bytes.ContainsAny([]byte("hello"), "")


Казалось бы, очевидно. Но разработчики разделились почти на четыре группы.

Подсказка: загляните в исходники стандартной библиотеки. Поведение ContainsAny с пустым chars задокументировано — но интуиция здесь часто подводит.

➡️ Правильный ответ в собесах по Go

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека Go-разработчика

#ReadySetGo
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2
📌 Зачем дата-сайентисту матанализ?

Основная компетенция специалиста по Data Science – способность анализировать и интерпретировать данные, а математика является фундаментом для начала работы.

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

Хотите подготовиться к офферу или подтянуть знания? Оставляйте заявку на наш курс по математике для Data Science 💙

P.S. Только до 31 мая на курс (и вообще на все программы Академии) действует СКИДКА 40%

А как у вас дела с высшей математикой?
❤️ — Помню всё
🔥 — Знаю основы
🌚 — Ничего не знаю

🏃‍♀️ Proglib Academy
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥42🌚2
📎 sync.Pool в Go, но с дженериками и типобезопасностью

В стандартной библиотеке Go есть sync.Pool. Штука простая по идее, но полезная на практике. Она позволяет переиспользовать объекты вместо того, чтобы каждый раз выделять под них новую память. Меньше аллокаций, меньше нагрузка на GC, выше производительность.

Как это работает

Представьте скоростной принтер, который печатает 100 страниц в минуту. Было бы странно, если бы он перед каждой страницей бегал на склад за новой пачкой бумаги. Вместо этого у него есть лоток на 100 листов. Берём лист, печатаем, кладём обратно. sync.Pool работает по тому же принципу.

Вот как это выглядит в коде:
var paperPool = sync.Pool{
New: func() interface{} {
return new(Paper)
},
}

func printPage() {
page := paperPool.Get().(*Paper)
page.Reset()

defer paperPool.Put(page)

page.Print()
}


Несколько важных моментов про sync.Pool.

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

Ещё один момент. Объекты могут хранить состояние от предыдущего использования. Поэтому перед использованием или перед возвратом в пул их нужно сбрасывать. Если этого не делать, можно получить трудноуловимые баги с «грязными» данными.

Типобезопасная обёртка

sync.Pool хранит и возвращает interface{}. Это гибко, но не безопасно. Ничто не мешает положить в пул строку, а достать и попытаться привести к структуре. Приложение упадёт с паникой.

Дженерики в Go позволяют обернуть sync.Pool так, чтобы тип контролировался на этапе компиляции.
type Pool[T any] struct {
internal sync.Pool
}

func NewPool[T any](newF func() T) *Pool[T] {
return &Pool[T]{
internal: sync.Pool{
New: func() interface{} {
return newF()
},
},
}
}


Мы создали обёртку, привязанную к конкретному типу T. Под капотом всё тот же sync.Pool с interface{}, но снаружи пользователь работает только с типом T.

Теперь методы Get и Put:
func (p *Pool[T]) Get() T {
return p.internal.Get().(T)
}

func (p *Pool[T]) Put(x T) {
p.internal.Put(x)
}


В Get мы приводим interface{} к типу T без проверки ошибки. И это нормально. Наша обёртка гарантирует, что в пул попадают только значения типа T. Функция New создаёт T, метод Put принимает только T. Поэтому приведение типа p.internal.Get().(T) не вызовет панику при штатном использовании.

Код остаётся чистым, типобезопасным и не требует ручных проверок на каждый Get.

Обёртка с дженериками убирает необходимость в ручном приведении типов и снижает риск ошибок. Решение занимает десяток строк, при этом полностью совместимо со стандартным sync.Pool.

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека Go-разработчика

#GoDeep
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7
😌 Дайджест недели

Осталось 7 дней до лета!

Две уязвимости в golang.org/x/image

В пакете golang.org/x/image исправлены две CVE. Первая вызывала панику при декодировании BMP с некорректным индексом палитры. Вторая позволяла через специально сформированный TIFF-файл заставить декодер потреблять непропорционально много ресурсов при распаковке PackBits.

Шесть уязвимостей в golang.org/x/net

В пакете golang.org/x/net закрыты шесть CVE. Четыре связаны с XSS через ошибки HTML-парсера, одна позволяет устроить отказ в обслуживании при парсинге HTML, ещё одна открывает возможность обхода проверки доменных имён через некорректную обработку Punycode в пакете idna.

Vibe теперь и hiring

Go 1.27 заморожен

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека Go-разработчика

#GoLive
Please open Telegram to view this post
VIEW IN TELEGRAM
👨‍💻 Скрытая цена чрезмерной абстракции в Go

Интерфейсы повсюду, многослойные пакеты, DI-контейнеры, generic-репозитории. Код выглядит солидно на ревью. Но стоит починить баг, и вы проходите шесть слоёв абстракции ради пяти строк логики. В Go это обходится дороже, чем кажется.

Почему это происходит

Интерфейсы в Go не требуют implements, любой тип удовлетворяет им неявно. Это удобно, но создаёт ловушку. Интерфейсы легко определять, поэтому их определяют слишком много. Пакеты легко создавать, поэтому кодовая база обрастает слоями ради диаграммы, а не ради задачи.

Чем это бьёт

Вы видите s.repo.Find(ctx, query.NewUserQuery().WithID(id)) и не знаете, это база, кэш или мок. Индирекция без пользы, просто шум. Интерфейсы создаются под единственную реализацию ещё до первого теста. Тесты обрастают моками для моков. Файл теста длиннее кода, который он тестирует.

Как оставаться простыми

Начинайте с конкретики. Напишите структуру, заставьте работать. Интерфейс выносите, когда появится вторая реализация. Так устроена стандартная библиотека Go. io.Reader не определили заранее:
type UserStore struct {
db *sql.DB
}

func (s *UserStore) GetByID(ctx context.Context, id string) (*User, error) {
// реальная реализация
}

// Интерфейс только когда нужен
type UserGetter interface {
GetByID(ctx context.Context, id string) (*User, error)
}


Пакеты должны делать, а не транслировать. Если пакет только переводит между другими пакетами, это шов без назначения. Для большинства сервисов плоская структура cmd/, internal/store/, internal/api/ работает лучше.

Функции вместо интерфейсов, где уместно. Не каждое поведение нужно выражать через интерфейс:
func SendWelcome(ctx context.Context, user User, send func(context.Context, string, string) error) error {
return send(ctx, user.Email, welcomeMessage(user))
}


Называйте по действию, а не по слою. UserActivator, SessionIssuer, PasswordResetter вместо UserService, UserManager, UserHandler. Точность в имени сопротивляется желанию сваливать всё в одну абстракцию.

Простой симптом. Если для одной фичи вы трогаете интерфейс, структуру, конструктор, мок и фикстуру до написания логики, архитектура работает против вас.

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека Go-разработчика

#GoDeep
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7🌚5😁2🥱1
До 31 мая можно забрать любой курс Proglib Academy со скидкой 40%

Если давно хотели прокачаться в Python, ML, алгоритмах или AI-агентах, сейчас самое время выбрать программу и начать обучение по сниженной цене.

🎁 Разработка AI-агентов от 49.000 ₽ (вместо 69.000 ₽)

Практический курс по разработке AI-агентов для автоматизации задач, работы и собственных проектов

🎁 Курс AgentOps129.000 ₽ (вместо 149.000 ₽)

Для разработчиков и LLM-инженеров, которые хотят внедрять AI-логику в бэкенд и сохранять стабильность сервиса.

🎁 Математика для разработки AI-моделей 23.990 ₽ (вместо 31.990 ₽)

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

🎁 Математика для Data Scienceот 29.990 ₽ (вместо 39.990 ₽)

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

🎁 ML для старта в Data Science28.990 ₽ (вместо 38.990 ₽)

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

🎁 Основы IT для непрограммистов16.990 ₽ (вместо 28.990 ₽)

Курс для IT-рекрутеров, маркетологов, проджектов, продактов и всех, кто работает с IT, но не пишет код.

🎁 Архитектуры и шаблоны проектирования27.990 ₽ (вместо 37.900 ₽)

Освоите основные паттерны проектирования и прокачаете навыки архитектора программного обеспечения.

🎁 Специалист по ИИ89.000 ₽ (вместо 113.900 ₽)

Курс для тех, кто хочет получить профессию в сфере ИИ, собрать портфолио из 5 проектов и научиться разрабатывать сложных AI-агентов.

🎁 Алгоритмы и структуры данных 33.990 ₽ (вместо 57.990 ₽)

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

🎁 Программирование на языке Python27.990 ₽ (вместо 47.390 ₽)

Освоите Python на практике: без сухой теории, с пошаговой прокачкой навыков и итоговым проектом в портфолио.

🙌 Выбирайте курс по ссылке, оставляйте заявку, и менеджер поможет подобрать программу под ваши цели — https://clc.to/U79QHw
🆚 Git merge и rebase. В чём разница и когда что использовать

Обе команды решают одну задачу — подтянуть изменения из одной ветки в другую. Но делают это совершенно по-разному. Понимание этой разницы убирает половину путаницы при работе с ветками.

Ситуация

Вы работаете в feature-ветке. Пока вы пишете код, кто-то вливает новые коммиты в main. Ваша ветка отстала. Нужно обновиться.

Тут два варианта. Первый — git merge main. Второй — git rebase main. Оба подтянут свежие изменения, но история коммитов будет выглядеть по-разному.

Что делает merge

Merge берёт изменения из обеих веток и объединяет их в один новый коммит. Этот коммит называется merge commit:
git checkout feature
git merge main


Git создаёт точку слияния, где обе линии разработки сходятся. История сохраняется как есть — видно, когда ветки разошлись и когда соединились.

Это безопасный вариант. Ничего не перезаписывается, старые коммиты остаются на месте. Для новичков merge проще, потому что конфликты решаются один раз.

Минус проявляется на больших проектах. Когда разработчиков много, лог засоряется merge-коммитами вида «Merge branch 'main' into feature». Через пару месяцев читать такую историю становится тяжело.

Что делает rebase

Rebase переносит ваши коммиты поверх последнего состояния целевой ветки. Вместо объединения историй он перестраивает вашу ветку так, будто вы начали работу с самой свежей версии main:
git checkout feature
git rebase main


Git временно снимает ваши коммиты, обновляет ветку до актуального main, а потом накатывает ваши коммиты по одному заново. При этом коммиты пересоздаются — у них меняются хеши. Коммит C становится C', F становится F'. Именно поэтому говорят, что rebase переписывает историю.

Результат — чистая линейная история без лишних merge-коммитов. Читать лог проще, ревью и дебаг тоже упрощаются.

Главное правило rebase

Не делайте rebase на ветках, с которыми работают другие люди. Rebase переписывает коммиты, и если кто-то уже забрал старые версии, возникнет каша из конфликтов.

Безопасно — rebase своей локальной feature-ветки. Опасно — rebase общей ветки, в которую пушат несколько человек.

Конфликты

Обе команды могут вызвать конфликты. Разница в том, как вы их решаете. При merge конфликт обычно один — в момент слияния. При rebase Git может останавливаться на каждом переносимом коммите. Это чуть утомительнее, но итоговая история получается чище.

Что используют на практике

Многие команды комбинируют оба подхода. Типичная схема выглядит так. Локально вы делаете git rebase main на своей feature-ветке, чтобы держать историю чистой. А когда feature готова, вливаете её в main через git merge или pull request.

Такой подход даёт чистую историю в feature-ветке и безопасное слияние в общую.

Merge сохраняет полную картину — кто, когда и откуда вливал изменения. Rebase делает историю линейной и читаемой. Ни один из подходов не лучше другого в абсолюте. Выбор зависит от того, что важнее вашей команде — полнота истории или её чистота.

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека Go-разработчика

#GoToProduction
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1