fa
Feedback
Библиотека Go-разработчика | Golang

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

رفتن به کانال در Telegram

Все самое полезное для Go-разработчика в одном канале. Учиться у нас: clc.to/qaSdww По рекламе: @proglib_adv Для обратной связи: @proglibrary_feeedback_bot РКН: https://gosuslugi.ru/snet/67a4a8c24689c2151c752af0 #WXSSA

نمایش بیشتر

📈 تحلیل کانال تلگرام Библиотека Go-разработчика | Golang

کانال Библиотека Go-разработчика | Golang (@goproglib) در بخش زبانی روسی بازیگری فعال است. در حال حاضر جامعه شامل 23 949 مشترک است و جایگاه 5 620 را در دسته فناوری و برنامه‌ها و رتبه 27 758 را در منطقه روسيا دارد.

📊 شاخص‌های مخاطب و پویایی

از زمان ایجاد در невідомо، پروژه رشد سریعی داشته و 23 949 مشترک جذب کرده است.

بر اساس آخرین داده‌ها در تاریخ 28 ژوئن, 2026، کانال فعالیت پایداری دارد. در ۳۰ روز گذشته تغییر اعضا برابر -86 و در ۲۴ ساعت گذشته برابر -4 بوده و همچنان دسترسی گسترده‌ای حفظ شده است.

  • وضعیت تأیید: تأیید نشده
  • نرخ تعامل (ER): میانگین تعامل مخاطب 11.88% است و در ۲۴ ساعت نخست پس از انتشار، محتوا معمولاً 7.58% واکنش نسبت به کل مشترکان کسب می‌کند.
  • دسترسی پست‌ها: هر پست به طور میانگین 2 844 بازدید دریافت می‌کند. در اولین روز معمولاً 1 816 بازدید جمع‌آوری می‌شود.
  • واکنش‌ها و تعامل: مخاطبان به‌طور فعال حمایت می‌کنند؛ میانگین واکنش به هر پست 10 است.
  • علایق موضوعی: محتوا بر موضوعات کلیدی مانند навигация, лучшее_из_библиотеки_2025, git, string, golive تمرکز دارد.

📝 توضیح و سیاست محتوایی

نویسنده این فضا را محل بیان دیدگاه‌های شخصی توصیف می‌کند:
Все самое полезное для Go-разработчика в одном канале. Учиться у нас: clc.to/qaSdww По рекламе: @proglib_adv Для обратной связи: @proglibrary_feeedback_bot РКН: https://gosuslugi.ru/snet/67a4a8c24689c2151c752af0 #WXSSA

به لطف به‌روزرسانی‌های پرتکرار (آخرین داده در تاریخ 29 ژوئن, 2026)، کانال همواره به‌روز و دارای دسترسی بالاست. تحلیل‌ها نشان می‌دهد مخاطبان به‌طور فعال با محتوا تعامل دارند و آن را به نقطه اثرگذاری مهم در دسته فناوری و برنامه‌ها تبدیل کرده‌اند.

23 949
مشترکین
-424 ساعت
-77 روز
-8630 روز
آرشیو پست ها
🤩 Фаззинг находит то, что вы не догадались проверить У обычных тестов есть слепое пятно. Они проверяют случаи, которые вы придумали, а баги живут в тех, что не придумали. Покрытие в 90% говорит, сколько строк вы прогнали, но молчит о том, сколько форм входа упустили. Зелёный прогон означает лишь, что код отработал на ваших примерах, а не что устоит против всех. ➡️ Идея в одном сдвиге Вы пишете не пример, а свойство, которое обязано держаться всегда. Машина сама ищет вход, который его ломает. Вместо «на входе X жду Y» вы описываете инвариант:
func FuzzParse(f *testing.F) {
    f.Add("platform=5000") // сиды это ваши готовые кейсы
    f.Fuzz(func(t *testing.T, rule string) {
        team, limit, err := ParseBudgetRule(rule)
        if err != nil {
            return // ошибка на кривом входе это норма
        }
        // раз ошибки нет, результат обязан быть валидным
        if limit <= 0 || team == "" {
            t.Errorf("bad result without error for %q", rule)
        }
    })
}
Частое заблуждение, что фаззер сыплет случайные байты. Современные фаззеры coverage‑guided. Стартуют с ваших сидов и мутируют их, отслеживая, какие мутации открывают новые ветки. Попал в новый путь, копает оттуда. Поэтому к багам он сходится в разы быстрее слепого рандома. Юнит‑тесты проверяют дороги, которые вы построили, фаззер ищет те, о которых вы не знали. ❓ Что ловит Класс ошибок на стыке кода и реальности. Паника на входе, которого «не бывает».
parts := strings.SplitN(rule, "=", 2)
limit, _ := strconv.Atoi(parts[1]) // "platform" без = и parts[1] не существует
// panic: index out of range [1] with length 1
Нарушения инвариантов, когда функция вернула мусор без ошибки, что хуже явной паники. Расхождения round‑trip, когда decode(encode(x)) не равно x. Их объединяет одно. Все появляются, когда снаружи приходит то, чего код не ждал. Обычные тесты это скипают, потому что их пишут под ожидаемые сценарии, а вы рассуждаете как автор кода, который знает, как им пользоваться. Фаззеру эта рамка незнакома. Найденный падающий вход не исчезает. Go сам кладёт минимальный контрпример в testdata/fuzz/ и делает из него регрессию:
go test -fuzz=FuzzParse -fuzztime=30s   # ищем баг
go test ./...                           # корпус гоняется всегда, даже без -fuzz
Один раз пойманный баг вернуться уже не может. ➡️ Куда прикладывать Туда, где код разбирает или валидирует внешний вход. Парсеры, декодеры, заголовки, query, конфиги, всё, что ветвится по содержимому. Чем больше условной логики, тем больше путей фаззеру. Не стоит фаззить чистую бизнес‑логику без зависимости от входа и функции, которые ходят в сеть или базу, их фаззер дёрнет тысячи раз. Правило короткое. Зависит поведение от того, что прислал внешний мир, заведите фаззинг. ❓ Почему окупается Барьер почти нулевой. В Go фаззер встроен в тулчейн с версии 1.18, свойство пишется в считаные строки. Цена это пара минут на разработке. Альтернатива это ждать, пока вход за вас подберёт прод. Боевыми данными, под нагрузкой, в три часа ночи, в виде алерта. Прод пофаззит ваш код в любом случае, вопрос лишь в том, узнаете вы о баге от теста или от дежурного. 📍 Навигация: ВакансииЗадачиСобесы 🐸 Библиотека Go-разработчика #GoDeep

📎 Главная проблема в микросервисах Сервис стартует чистым. Через полгода его трогать никто не хочет. Багов нет, тесты проходят, но любое изменение задевает пять файлов, а новому инженеру три дня объясняют, что где лежит. Проблема не в алгоритме и не в кэше, а в структуре. Команды копируют раскладку папок, не понимая контракт, который эта раскладка должна навязывать. Папки вместо архитектуры Спросите десять команд про структуру сервиса, и почти все опишут что‑то такое.
/cmd
/internal
  /handlers
  /services
  /repository
  /models
/pkg
Выглядит разумно и проходит ревью. Беда в том, что папки создают иллюзию границ, но не держат их. Ничто не мешает хендлеру импортировать репозиторий напрямую. Ничто не мешает модели обрасти бизнес‑логикой. Границы косметические, и код деградирует предсказуемо. Логика переползает туда, где удобнее, обычно в хендлеры, потому что там контекст. Типы репозитория протекают в API, потому что заводить отдельный DTO лень. Пакет models превращается в общий мешок структур, от которого зависят все, и поменять в нём что‑либо без каскада уже нельзя. Получается слоёная на вид архитектура, ведущая себя как монолит с накладными расходами на HTTP. В чём контракт слоёв Контракт про направление зависимостей. Они текут внутрь. Внешний слой (HTTP, gRPC, CLI) знает про слой приложения. Слой приложения знает про домен. Домен не знает ни про что. На практике это значит вот что. Хендлер принимает запрос, зовёт метод сервиса и сериализует ответ. Никакого SQL и никаких бизнес‑правил в нём нет. Сервис кодирует, что приложение делает. Он оркестрирует, валидирует, делегирует репозиторию и ничего не знает про HTTP. Репозиторий говорит с базой и возвращает доменные типы или ошибки, но не pgx.Rows. Доменные типы несут инварианты. Структура Member знает, что делает участника валидным, но не знает, как сериализовать себя в JSON. Когда контракт держится, слои тестируются независимо, реализацию репозитория можно подменить, не трогая хендлер, а gRPC добавить без дублирования логики. Анти‑паттерн god service Чаще всего контракт ломает god service. Сервис‑структура, которая всасывает всё:
type MemberService struct {
    db *pgxpool.Pool
}

func (s *MemberService) CreateMember(ctx context.Context, req CreateMemberRequest) (*Member, error) {
    // валидация, хеш, вставка строки, welcome‑письмо, событие в Kafka
}
Через полгода у MemberService сорок методов, пул базы, почтовый клиент, продюсер Kafka, клиент S3 и конфиг. Любой тест требует поднять все зависимости, даже когда проверяешь одну ветку валидации. Это уже не слой, а ящик для хлама. И дробить его на MemberCreationService и MemberUpdateService бесполезно, получите ящики поменьше. Лечит инверсия зависимостей Интерфейсы в Go удовлетворяются неявно, и это недоиспользуют. Вместо *pgxpool.Pool опишите то, что сервису реально нужно:
type MemberRepository interface {
    CreateMember(ctx context.Context, params CreateMemberParams) (*Member, error)
    GetMemberByID(ctx context.Context, id uuid.UUID) (*Member, error)
}

type EventPublisher interface {
    Publish(ctx context.Context, event DomainEvent) error
}

type MemberService struct {
    repo      MemberRepository
    publisher EventPublisher
}
Теперь сервис ничего не знает про PostgreSQL и Kafka. Он зависит от поведения, а не от реализации. Чтобы протестировать метод, достаточно мока под интерфейс, без базы, брокера и сети. Про pgx знает реализация репозитория, про franz-go знает реализация publisher, а сервис не знает ни про то, ни про другое. В этом и есть контракт. 📍 Навигация: ВакансииЗадачиСобесы 🐸 Библиотека Go-разработчика #GoDeep

🎮 Субботний оффтоп Кто-то собрал веб-версию Half-Life 2 и запустил её без установки и скачивания. Уровни и ресурсы грузятся
🎮 Субботний оффтоп Кто-то собрал веб-версию Half-Life 2 и запустил её без установки и скачивания. Уровни и ресурсы грузятся быстро, игра работает бодро. У многих сразу включается русский язык — Сити-17 и Рейвенхольм уже вовсю тестируют первые игроки. ➡️ Поиграть 📍 Навигация: ВакансииЗадачиСобесы 🐸 Библиотека Go-разработчика #GoLive

👨‍💻 Контекст неизменяемый, а память нет Правило знают все. context.WithValue ничего не меняет, а создаёт новую обёртку, поэтому контекст безопасно шарить между горутинами. Учебники забывают сказать главное. Иммутабельны обёртки, а объект в корне цепочки может переиспользоваться фреймворком через sync.Pool. Тогда горутина, пережившая запрос, держит указатель на память, которая вот‑вот достанется другому запросу. Хендлер запускает фоновую задачу и сразу возвращает ответ. Линтер ругается на отмену контекста, и со времён Go 1.21 его успокаивают одной строкой:
go s.processAsync(context.WithoutCancel(ctx), id)
Для net/http это правильно. Там контекст это обычная аллокация в куче. Но линтер слеп к архитектуре и не знает, что под context.Context может стоять пулящаяся структура. ❓ Где можно споткнуться В Fiber и всём на fasthttp c.Context() возвращает *fasthttp.RequestCtx. Пулящийся объект и есть ваш контекст, и после отправки ответа fasthttp сбрасывает его и кладёт обратно в пул. Gin пулит *gin.Context, поэтому передавать его в горутину нельзя (для снимка есть c.Copy()), а c.Request.Context() безопасен. Echo свой контекст пулит, но context.Context он не реализует, так что задеть гонку сложнее. Дальше воркер просыпается после I/O, зовёт ctx.Value("user_id") и читает память, которую перезаписывают данными другого пользователя. И вот что коварно. WithoutCancel снимает только отмену, а Value() по‑прежнему делегирует пулящемуся родителю. То есть фикс, который позеленил линтер, ровно и удерживает протухший указатель. ➡️ Как правильно Если задаче ничего не нужно из запроса, постройте свежий корень:
ctx := context.Background()
Если нужны trace‑id или user‑id, вытащите их как простые значения, пока горутина запроса ещё жива, и положите в свежий контекст:
userID := c.Locals("user_id").(string)
traceID := c.Locals("trace_id").(string)
go func(userID, traceID string) {
    ctx := context.WithValue(context.Background(), userCtxKey, userID)
    ctx = context.WithValue(ctx, traceCtxKey, traceID)
    h.svc.ProcessAsync(ctx, userID)
}(userID, traceID)
Вся безопасность держится на том, что значения сняты до возврата из хендлера. Логику стоит вынести в хелпер вроде ctxutil.Detach(ctx). Из штатных лазеек помогают c.Copy() у Gin и c.UserContext() у Fiber. Линтер видит синтаксис, но не жизненный цикл памяти. Если горутина переживает запрос, не давайте ей контекст запроса ни в какой обёртке. Снимайте значения как простые данные, стройте свежий корень и держите -race в CI. 📍 Навигация: ВакансииЗадачиСобесы 🐸 Библиотека Go-разработчика #GoDeep

🎬 Где ломаются архитектуры ИИ-агентов и как этого избежать: запись урока от Proglib.Academy и cloud․ru Proglib.аcademy вмест
🎬 Где ломаются архитектуры ИИ-агентов и как этого избежать: запись урока от Proglib.Academy и cloud․ru Proglib.аcademy вместе с cloud․ru провели вебинар, где разобрали реальные боли проектирования автономных систем. Вы просили запись встречи — она уже в открытом доступе! Что внутри: — критерии выбора между одним агентом и мультиагентной системой; — разбор популярных архитектурных ошибок; — реальные ограничения современных ИИ-агентов; — практические рекомендации по проектированию агентных систем. 👉 Посмотреть запись можно тут: VKYouTube

🐠 Открытый фреймворк для учебного фишинга Gophish это опенсорсный инструмент для проведения фишинговых учений и обучения сот
🐠 Открытый фреймворк для учебного фишинга Gophish это опенсорсный инструмент для проведения фишинговых учений и обучения сотрудников. Он рассчитан на бизнес и пентестеров, которым нужно легально проверить, как команда реагирует на поддельные письма. Проект написан на Go. Запускать такие кампании можно только против своей организации или там, где у вас есть письменное разрешение. Без него это уже не учения. Вы описываете кампанию в веб-интерфейсе, а инструмент сам рассылает письма, поднимает посадочные страницы, отслеживает открытия и переходы и собирает всё в отчёты. Вся функциональность доступна и через REST API, поэтому кампании можно гонять из скриптов и встраивать в свои пайплайны отчётности. ➡️ Как запустить Самый быстрый путь это готовый бинарь. Под Windows, macOS и Linux есть релизы, их достаточно скачать, распаковать и запустить. Или собрать самостоятельно:
git clone https://github.com/gophish/gophish.git
cd gophish
go build
./gophish
Есть и официальный Docker образ, если не хотите ставить ничего на хост:
docker run --name gophish -p 3333:3333 -p 8080:8080 gophish/gophish
После старта админка поднимается на https://localhost:3333. Логин и пароль не зашиты заранее, а печатаются в лог при первом запуске. Выглядит это примерно так:
level=info msg="Please login with the username admin and the password 4304d5255378177d"
Gophish превращает разрозненную ручную работу по фишинговым учениям в управляемый процесс с нормальной статистикой. ➡️ Репозиторий 📍 Навигация: ВакансииЗадачиСобесы 🐸 Библиотека Go-разработчика #GoToProduction

🤔 Функция с пустым телом — что вернёт компилятор? Вот простой код:
func foo() {}
Тело есть. Но оно пустое. Функция ничего не делает, ничего не возвращает. Go — язык строгий. Он не любит неиспользуемые переменные и лишний импорт. Но как он относится к функции, которая существует и при этом ничего не делает? Подсказка: вспомните, как Go обрабатывает неиспользуемый код на уровне функций, а не переменных. ➡️ Правильный ответ 📍 Навигация: ВакансииЗадачиСобесы 🐸 Библиотека Go-разработчика #ReadySetGo

🤖 Запускаем новый курс: «Spec-Driven Development»! Всем надоело, что ИИ-агенты (Cursor, Claude) генерируют сотни строк хаоти
🤖 Запускаем новый курс: «Spec-Driven Development»! Всем надоело, что ИИ-агенты (Cursor, Claude) генерируют сотни строк хаотичного кода, который приходится переписывать. Дело не в нейросети, а в том, что вы заставляете её угадывать условия задачи. На курсе вы освоите методологию SDD (Spec-Driven Development) — управление ИИ через спецификации (спеки) и тесты вместо «вайб-кодинга». Как это работает? До генерации кода вы фиксируете в спеке контракты, инварианты и граничные случаи. Документ четко определяет ЧТО делать, сводя лотерею в PR к нулю. За 8 недель с экспертами из BigTech вы: 🔹 Встроите ИИ в личный или командный workflow. 🔹 Превратите генерацию кода в предсказуемый инженерный процесс. 🔹 Перестанете перепроверять за моделями каждую строчку. 📖 Полный разбор методологии с примерами и готовый промпт для генерации спеки — в нашей статье 👉 Освоить SDD и ускорить разработку

💡 Предложение добавить slog.TestHandler в стандартную библиотеку Go В трекере Go появилось предложение добавить в пакет log/slog готовый хендлер, который пишет логи в testing.TB. Тогда логи приложения и логи теста окажутся в одном выводе и привяжутся к нужному тесту или подтесту. Зачем это Сопоставлять логи приложения с тестом, который их породил, приходится постоянно. Кирпичик для этого уже есть. Метод testing.TB.Output() добавили в Go 1.25 для T, B и F, а в Go 1.26 расширили на сам интерфейс TB. Но готового хендлера в slog нет, поэтому каждый проект собирает его заново. Обычно так:
logger := slog.New(slog.NewTextHandler(t.Output(), nil))
Этот вариант работает, но у него два минуса. Удобства тест ориентированного хендлера приходится дописывать руками, а наивная форма имеет реальную гонку при логировании после завершения теста. ➡️ Что предлагают сделать В log/slog появится тип TestHandler, реализующий интерфейс Handler, и конструктор к нему. Чтобы не тянуть testing внутрь slog и не словить циклический импорт, вводят узкий интерфейс TestLogger. Ему удовлетворяют *testing.T, *testing.B и *testing.F.
package slog

// TestLogger — подмножество testing.TB, нужное хендлеру.
type TestLogger interface {
    Output() io.Writer
}

// NewTestHandler возвращает Handler, который пишет записи в tb.Output().
// Вывод форматируется как у NewTextHandler. opts может быть nil.
func NewTestHandler(tb TestLogger, opts *HandlerOptions) *TestHandler

type TestHandler struct {
    tb TestLogger // куда писать
    // ...
}

func (h *TestHandler) Enabled(ctx context.Context, level Level) bool { ... }
func (h *TestHandler) Handle(ctx context.Context, record Record) error { ... }
func (h *TestHandler) WithAttrs(attrs []Attr) Handler { ... }
func (h *TestHandler) WithGroup(name string) Handler { ... }
На стороне теста всё сводится к одной строке:
func TestThing(t *testing.T) {
    logger := slog.New(slog.NewTestHandler(t, nil))

    // Логи лягут под TestThing с отступом от раннера.
    // Для прошедших тестов вывод скрыт, пока не передан флаг -v,
    // как у t.Log() и t.Logf().
    doWork(logger)
}
Пока это обсуждение, а не принятая фича. Следить за судьбой можно в самом issue #80138. 📍 Навигация: ВакансииЗадачиСобесы 🐸 Библиотека Go-разработчика #GoLive

⭐️ Фреймворк для микросервисов на Go Когда вы пишете микросервис на Go, заметная доля времени уходит не на бизнес логику, а на обвязку вокруг неё. Загрузка конфигов, логирование, трейсинг, метрики, подключение к базам, health чеки. Каждый новый сервис тащит этот набор заново, и в каждой команде он немного свой. GoFr берёт обвязку на себя. Это фреймворк, заточенный под микросервисы и деплой в Kubernetes. ❓ Как это выглядит Минимальное приложение умещается в десяток строк. Сначала ставите модуль:
go get -u gofr.dev/pkg/gofr
Дальше создаёте приложение и вешаете маршрут:
package main

import "gofr.dev/pkg/gofr"

func main() {
  app := gofr.New()

  app.GET("/greet", func(ctx *gofr.Context) (any, error) {
    return "Hello World!", nil
  })

  app.Run() // слушает на localhost:8000
}
После этого по адресу localhost:8000/greet уже отвечает рабочий сервис. Обработчик возвращает данные и ошибку, а сериализацию ответа по стандартам REST фреймворк делает сам. ➡️ Что внутри Главная фишка это наблюдаемость из коробки. Логи, трейсы и метрики собираются без ручной настройки, под капотом OpenTelemetry. Конфиги читаются из переменных окружения и файлов, так что код не привязан к окружению. Подключения к базам данных идут через объект ctx, и для каждого источника данных есть health чек. Фреймворк закрывает типичные потребности микросервиса: поддержка gRPC, HTTP клиент с circuit breaker, Pub/Sub, миграции базы, cron задачи, аутентификационный middleware и место под свой, смена уровня логирования без перезапуска, рендеринг Swagger, абстракция над файловыми системами, веб сокеты. GoFr подойдёт, если вы строите много однотипных сервисов и устали раз за разом писать одну и ту же инфраструктурную обвязку. Взамен вы принимаете правила фреймворка, потому что он именно опинионированный и навязывает свой способ делать вещи. Для разовой утилиты или нестандартной архитектуры это может оказаться лишним, а вот для парка микросервисов под Kubernetes экономия времени ощутимая. ➡️ Репозиторий 📍 Навигация: ВакансииЗадачиСобесы 🐸 Библиотека Go-разработчика #GoToProduction

👀 Умеете строить предсказуемую архитектуру с ИИ? Станьте спикером Proglib academy Мы в Proglib.academy запускаем курс по Spe
👀 Умеете строить предсказуемую архитектуру с ИИ? Станьте спикером Proglib academy Мы в Proglib.academy запускаем курс по Spec-Driven Development (SDD) — учим разработчиков управлять ИИ-агентами (Cursor, Copilot, Claude) через спецификации, контекст и тесты, чтобы не перепроверять за нейросетью каждую строку.
Для этого курса мы ищем классных спикеров-практиков.
➡️ Что требуется от вас?BigTech бэкграунд: опыт работы разработчиком, тимлидом или архитектором в крупных технологических компаниях. ● Опыт управляемой разработки с ИИ: вы на практике знаете, как встроить AI-инструменты в личный или командный workflow (от постановки задачи до ревью AI-кода). ● Системный подход: умение превращать хаотичную генерацию кода в предсказуемый инженерный процесс (работа с архитектурой, чек-листами, легаси и покрытием тестами). ➡️ Что мы предлагаем? ● Достойную оплату за подготовку материалов и проведение занятий. ● Мощное продвижение личного бренда через медиаресурсы Proglib (наша аудитория — 1 млн+ айтишников). ● Возможность публиковать свои экспертные материалы и статьи на наших площадках. ● Доступ к сильному и закрытому профессиональному сообществу. Также мы ищем консультанта программы. Если вы практикующий эксперт и готовы помочь нам отвалидировать программу курса, дать рекомендации по актуальности тем и финальным результатам обучения — мы вас очень ждем. ➡️ Как с нами связаться: Telegram: @alinaa_kh E-mail: alina@proglib.io

🔄 gcli v3.8.0, общие опции и генерация документации gcli это Go библиотека для консольных приложений. Она берёт на себя разбор команд и флагов, цвет в терминале, диалоги с пользователем, прогресс бары и генерацию автодополнения для bash и zsh. 🌻 Общие опции для подкоманд Раньше, если один и тот же флаг нужен нескольким подкомандам, его приходилось объявлять в каждой заново. В 3.8.0 появилась трёхслойная модель опций. Через Command.SharedOpts вы задаёте общие флаги один раз, и при разборе они подмешиваются в подкоманды. Слияние идемпотентно, локальная опция перекрывает общую, а проверка Required теперь смотрит на тип значения, а не просто на пустую строку. Рядом добавили примитив Parser.InheritOptsFrom для слияния наборов опций и аксессор CliOpt.TypeName() для доступа к имени типа. В выводе help унаследованные флаги теперь показываются отдельной группой Inherited Options, так что пользователю видно, откуда взялся флаг. 🌻 Генерация документации из команд Второе крупное изменение это пакет docgen. Из описания ваших команд можно собрать markdown через CmdMarkdown, AppMarkdown и MarkdownTree, либо man страницы через CmdMan и ManTree. Встроенная команда GenDoc экспортирует готовые md и man файлы без лишнего кода. Многострочные примеры в man теперь сохраняются как есть, а цветовые теги из примеров вычищаются перед записью. 🌻 Рефакторинг и фиксы Внутри вынесли AppOptions, и состояние разбора больше не висит на общем синглтоне для каждого приложения. Это убирает странности, когда в одном процессе крутится несколько приложений. Ещё поправили разворачивание анонимных встроенных структур неэкспортируемых типов в FromStruct. ➡️ Релиз 📍 Навигация: ВакансииЗадачиСобесы 🐸 Библиотека Go-разработчика #GoLive

💭 Топ-вакансий для Go-разработчиков за неделю Backend разработчик — от 450 000 ₽, удаленно. Руководитель группы разработки — офис в Москве. Go-разработчик — до 485 000 ₽, удаленно. Бустер — удалённо (не только Москва) ➡️ Еще больше топовых вакансий — в нашем канале Go jobs 🐸 Библиотека Go-разработчика #GoWork

Разрабатываете на Go больше четырёх лет? Сразу 7 наших команд ждут вас: Сервис Транспорта строит маршруты во всех продуктах 2
Разрабатываете на Go больше четырёх лет? Сразу 7 наших команд ждут вас:
Сервис Транспорта строит маршруты во всех продуктах 2ГИС, отображает пробки, дорожные события и автобусы на карте. Social сотворяет магию вокруг социального графа. Platform Backend Services превращает бизнес‑требования в платформенные сервисы: от идеи до запуска фич мобильными командами без лишних зависимостей. Web API решает все справочные задачи, управляет стилями карт и обратной связью от пользователей в продуктах. Сервис Рекламы создаёт техническую основу для продуктов рекламного направления. 2ГИС Логистика строит и пересчитывает маршруты с учётом пробок, погодных условий, типов транспорта, параметров груза и сложных логистических цепочек. ГеоПоток помогает бизнесу повышать прозрачность процессов и сокращать издержки.
Все вакансии на сайте Другие инженерные инсайты от 2ГИС → в Telegram-канале RnD

🤓 Паника уронила весь fan-out воркер История из боевых распределённых систем. Один кривой подписчик уронил доставку всем остальным, потому что паника в горутине без recover завершает весь процесс. Острая грань языка, про которую легко забыть. ➡️ Что случилось Был шлюз вебхуков, который параллельно рассылал события десяткам подписчиков. У одного оказался кривой URL, который в редком случае приводил к панике в HTTP-клиенте вместо ошибки. Раздача шла в голых горутинах без восстановления:
func fanOut(events []Event, subscribers []Subscriber) {
    var wg sync.WaitGroup
    for _, sub := range subscribers {
        wg.Add(1)
        go func(s Subscriber) {
            defer wg.Done()
            deliver(s, events) // паника здесь убивает весь процесс
        }(sub)
    }
    wg.Wait()
}
В Go паника в любой горутине, которую не перехватили через recover, завершает всю программу. Не важно, сколько других горутин в этот момент делают полезную работу. Про это легко забыть, потому что в туториалах паника обычно происходит в главной горутине, где последствия очевидны. Здесь же баг одного подписчика оборвал доставку всем. ➡️ Как починили Изоляция паники стала структурным паттерном, а не заплаткой по случаю. Появилась обёртка safeGo:
func safeGo(fn func()) {
    go func() {
        defer func() {
            if r := recover(); r != nil {
                log.Printf("recovered panic: %v\n%s", r, debug.Stack())
            }
        }()
        fn()
    }()
}
func fanOut(events []Event, subscribers []Subscriber) {
    var wg sync.WaitGroup
    for _, sub := range subscribers {
        wg.Add(1)
        safeGo(func() {
            defer wg.Done()
            deliver(sub, events)
        })
    }
    wg.Wait()
}
Теперь каждая долгоживущая или fan-out горутина проходит через обёртку вроде safeGo, и серверные gRPC-интерсепторы получили то же самое. Восстановление после паники переехало с верхнего HTTP-хендлера на каждую горутину, которую вы заводите сами. 📍 Навигация: ВакансииЗадачиСобесы 🐸 Библиотека Go-разработчика #GoToProduction

🐞 uuid.NewV7() теряет случайность в браузере В трекере Go появился любопытный баг: новый стандартный пакет uuid, который должен приехать в Go 1.27, на таргете js/wasm генерирует «бракованные» UUIDv7 — часть случайных бит всегда оказывается нулевой. 🌸 Как повторить Вызвать uuid.NewV7().String() и собрать программу под js/wasm (например, GOOS=js GOARCH=wasm go run main.go через Node). В результате на свет стабильно появляются значения такого вида: Обратите внимание на третий блок — он всегда 7000. Меняется только timestamp в начале и хвост, а вот этот фрагмент будто прибит гвоздями. Почему 7000 — это проблема Чтобы понять, что именно сломано, полезно вспомнить структуру UUID версии 7. Внутри 128 бит лежат:
48 бит — Unix-таймстамп в миллисекундах; 4 бита — номер версии (0111, то есть 7); 12 бит — случайные данные (rand_a); 2 бита — вариант (10); 62 бита — случайные данные (rand_b).
В строковом виде 019ee60f-29b3-7000-... блок 7000 — это как раз версия (7) плюс те самые 12 бит rand_a. И вот эти 12 бит вместо случайных стабильно равны нулю — 000. Страдает именно 12-битное поле rand_a. Это сужает круг подозреваемых — где-то на пути заполнения этого конкретного куска случайными байтами на js/wasm что-то идёт не так. 📍 Навигация: ВакансииЗадачиСобесы 🐸 Библиотека Go-разработчика #GoLive

⚙️ Десктопные приложения на Go с веб-интерфейсом и без CGo Glaze теперь полностью CGo-free. Это десктопный WebView-тулкит для
⚙️ Десктопные приложения на Go с веб-интерфейсом и без CGo Glaze теперь полностью CGo-free. Это десктопный WebView-тулкит для Go, который позволяет собирать нативные окна с интерфейсом на HTML/CSS/JS — и при этом обходится без C-тулчейна в сборке. Glaze вырос из проекта go-webview, но со временем заметно разошёлся с оригиналом и развивается как отдельная кодовая база со своими целями и API. 🌸 Как это работает без CGo Обычно привязки к нативным WebView требуют CGo, а значит — C-компилятора, и кросс-компиляция превращается в головную боль. Glaze идёт другим путём и использует purego — библиотеку для динамической линковки без CGo. Нативная часть — это webview/webview, тонкая обёртка над системными WebView. Glaze встраивает её бинарники в приложение, извлекает на диск и загружает в рантайме через purego. Итог — один кросс-платформенный бинарь без зависимости от C-тулчейна. Минимальный старт:
package main

import (
    "log"

    "github.com/crgimenes/glaze"
    _ "github.com/crgimenes/glaze/embedded"
)

func main() {
    w, err := glaze.New(true)
    if err != nil {
        log.Fatal(err)
    }
    defer w.Destroy()

    w.SetTitle("Glaze")
    w.SetSize(800, 600, glaze.HintNone)
    w.SetHtml("<h1>Hello from Glaze</h1>")
    w.Run()
}
🌸 Что делает его практичным: три хелпераBindMethods рефлексией пробегает по экспортируемым методам структуры и автоматически регистрирует их как JS-функции. Например, метод GetUserByID с префиксом api становится api_get_user_by_id. Это избавляет от ручного вызова Bind на каждый метод и даёт консистентный JS-API из обычного Go-сервиса. • RenderHTML рендерит именованный Go-шаблон (html/template) в строку для SetHtml, включая вложенные шаблоны. Удобно, когда хочется серверный рендеринг шаблонов в локальном приложении без поднятия HTTP-сервера. • AppWindow — пожалуй, самое интересное. Он оборачивает любой http.Handler в нативное окно, поднимая локальный loopback HTTP-сервер. Транспорт выбираемый: auto (unix-сокет на macOS/Linux, tcp на Windows), tcp или unix. На практике это значит, что готовое net/http-приложение можно почти без изменений превратить в десктопное — роутинг, шаблоны и ассеты остаются прежними. Нативный WebView без CGo плюс возможность переиспользовать net/http-приложение как десктоп — выглядит очень аккуратно и практично. ➡️ Репозиторий 📍 Навигация: ВакансииЗадачиСобесы 🐸 Библиотека Go-разработчика #GoToProduction

🧑‍💻 Утечка горутин из-за каналов, которые никто не закрывает Go делает конкурентность простой. Горутины дешёвые, каналы встроены в язык, а воркер-пул собирается меньше чем за 30 строк. Но из-за этой простоты в код проникают баги. Программа компилируется, тесты проходят, а ошибка всплывает только под нагрузкой или после нескольких часов работы. Одна из частых ошибок в воркер-пулах это утечка горутин. ❓ В чём проблема Вот воркер-пул, который выглядит вполне разумно:
func startWorkers(jobs <-chan Job) {
    for i := 0; i < 10; i++ {
        go func() {
            for job := range jobs {
                process(job)
            }
        }()
    }
}
Каждый воркер читает канал jobs и обрабатывает всё, что приходит. Чистый идиоматичный Go. Проблема в том, что если jobs никто не закрывает, все эти горутины блокируются навсегда. Цикл for range по каналу блокируется, пока канал не закрыт или пока не пришло значение. Если продюсер перестал слать данные, но не вызвал close(jobs), ваши 10 воркеров висят в памяти бесконечно. Они держат стек и не видны в метриках, пока вы явно не считаете число горутин. В долгоживущем сервисе это накапливается. Каждый раз, когда вы перезапускаете пул, например при перезагрузке конфига или новой пачке задач, не дренируя старый, вы добавляете утёкшие горутины. ➡️ Как чинить Продюсер закрывает канал, когда закончил, и эта ответственность лежит только на нём.
func runBatch(jobList []Job) {
    jobs := make(chan Job, len(jobList))
    // Продюсер владеет каналом и закрывает его по завершении
    go func() {
        defer close(jobs) // сработает даже при панике
        for _, job := range jobList {
            jobs <- job
        }
    }()
    var wg sync.WaitGroup
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            for job := range jobs {
                process(job)
            }
        }()
    }
    wg.Wait()
}
Здесь два важных момента: • во-первых, defer close(jobs) в горутине продюсера закрывает канал, даже если продюсер вышел раньше времени или упал с паникой при отправке задач. • во-вторых, sync.WaitGroup даёт чистую точку синхронизации. Вызов wg.Wait() блокируется, пока все воркеры не дочитают канал. Продюсер владеет каналом и закрывает его, всегда через defer. Быстрая диагностика на проде такая. Добавьте runtime.NumGoroutine() в health-эндпоинт. Если это число растёт за время жизни сервиса и не возвращается обратно, у вас утечка. В тестах самый простой способ это многократно гонять пул и следить за ростом числа горутин через runtime.NumGoroutine(). 📍 Навигация: ВакансииЗадачиСобесы 🐸 Библиотека Go-разработчика #GoToProduction