Когда нужно перенести элементы из одного слайса в другой, рука тянется к циклу с индексом. Это рабочий вариант, но многословный и легко ошибиться с границами. Для этой задачи в языке есть встроенная функция
copy.Сигнатура:
func copy(dst, src []Type) int
Функция берёт элементы из источника
src и пишет их в dst. Возвращает число скопированных элементов. Это число всегда равно минимуму из длин двух слайсов:src := []int{1, 2, 3, 4}
dst := make([]int, 2)
n := copy(dst, src)
fmt.Println(n, dst) // 2 [1 2]Здесь у 'dst' длина 2, поэтому скопировались только первые два элемента, а остаток источника просто проигнорировался. Никакой ошибки или паники при этом нет.
Это та деталь, на которой спотыкаются чаще всего.
copy пишет ровно в те ячейки, что уже есть в приёмнике, то есть ориентируется на его длину. Если создать слайс с нулевой длиной, но большим запасом capacity, копировать будет некуда:dst := make([]int, 0, 10)
n := copy(dst, []int{1, 2, 3})
fmt.Println(n, dst) // 0 []
Длина нулевая, поэтому результат тоже ноль. Чтобы copy сработал, получатель должен иметь нужную длину заранее:
src := []int{1, 2, 3}
dst := make([]int, len(src))
copy(dst, src)
fmt.Println(dst) // [1 2 3]Под капотом
copy это не цикл с поэлементным присваиванием, а перемещение блока памяти, по поведению близкое к memmove из языка C. Из этого следует важное свойство. Источник и приёмник могут пересекаться по памяти, и результат всё равно будет корректным. Наивное копирование в цикле в такой ситуации затёрло бы данные, которые ещё не успело прочитать, а copy справляется правильно:s := []int{1, 2, 3, 4, 5}
copy(s[1:], s[2:]) // сдвигаем хвост на одну позицию влево
fmt.Println(s) // [1 3 4 5 5]Именно поэтому
copy удобно использовать для сдвигов внутри одного слайса, например при удалении элемента:i := 2
copy(s[i:], s[i+1:])
s = s[:len(s)-1]
Строку можно копировать прямо в
[]byte, хотя строка формально не слайс. Это разрешённое исключение в языке. Берётся, как обычно, минимум из длин:dst := make([]byte, 5)
n := copy(dst, "hello world")
fmt.Println(n, string(dst)) // 5 hello
copy переносит сами значения элементов. Если элемент это указатель, слайс или мапа, в приёмник попадёт та же ссылка, а не отдельная копия вложенных данных. После копирования оба слайса будут указывать на одни и те же вложенные объекты, и изменение через один из них отразится на другом. Для глубокой копии вложенную структуру нужно клонировать отдельно.copy нужен, когда вы пишете в уже выделенный буфер, копируете только часть данных или сдвигаете элементы внутри слайса. Если задача в том, чтобы получить полную копию слайса целиком, в стандартной библиотеке есть slices.Clone. Это короткая обёртка над тем же make плюс copy, и она читается яснее:dst := slices.Clone(src)
copy берёт минимум из длин источника и приёмника, переносит элементы как блок памяти и возвращает число скопированных штук. Держите в голове два момента:
• результат зависит от длины приёмника, а не от его capacity, поэтому буфер задают через
make([]T, len(src)).• копия поверхностная, вложенные ссылочные данные остаются общими. С этими двумя правилами
copy становится простым и предсказуемым инструментом.📍 Навигация: Вакансии • Задачи • Собесы
#GoToProduction
Please open Telegram to view this post
VIEW IN TELEGRAM
👍12❤4😁2
Популярный совет в Go звучит разумно. Оборачивайте каждую ошибку, пока она всплывает по стеку вызовов, чтобы всегда знать, откуда она пришла:
func getUser(id string) (*User, error) {
u, err := db.QueryUser(id)
if err != nil {
return nil, fmt.Errorf("getUser: query failed: %w", err)
}
return u, nil
}В маленьком проекте это работает. Но в системе, где один запрос проходит через шесть сервисов, вы получаете строки ошибок вот такого вида:
processPayment: validateAccount: fetchBalance: getUser: query failed: context deadline exceeded
Это не наблюдаемость, а стектрейс, переодетый в сообщение об ошибке. Вы дублируете то, что структурированное логирование уже даёт (имя функции, файл, строку), и платите за аллокацию
fmt.Errorf на каждом несчастливом пути.Оборачивать ошибки на границах доверия. Это вызов другого сервиса, слой базы данных или внешняя зависимость. Внутри одного пакета пусть ошибка летит как есть, а работу делает логгер:
// На границе оборачиваем
func (s *PaymentService) Process(ctx context.Context, req *ProcessRequest) error {
if err := s.ledger.Debit(ctx, req.AccountID, req.Amount); err != nil {
return fmt.Errorf("debit account %s: %w", req.AccountID, err)
}
return nil
}
// Внутри пакета просто возвращаем
func validate(req *ProcessRequest) error {
if req.Amount <= 0 {
return ErrInvalidAmount
}
return nil
}
Оборачивание уместно на стыках, где теряется контекст. Внутри пакета оно лишь раздувает сообщения и аллокации. Применяйте его как эвристику под конкретную границу, а не как правило для каждой функции.
📍 Навигация: Вакансии • Задачи • Собесы
#GoToProduction
Please open Telegram to view this post
VIEW IN TELEGRAM
👍10❤8🤔2👾1
📰 Дайджест недели
Вот здесь будет текст для недельного дайджеста.
— Первый релиз кандидат Go 1.27
— Java догнала Go
— vscode-go v0.54.0
В июньском релизе расширения Go для VS Code появились интерактивные code actions — рефакторинги, которые запрашивают у пользователя ввод перед применением: добавление/удаление тегов к полям структуры, реализация интерфейса.
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека Go-разработчика
#GoLive
Вот здесь будет текст для недельного дайджеста.
— Первый релиз кандидат Go 1.27
— Java догнала Go
— vscode-go v0.54.0
В июньском релизе расширения Go для VS Code появились интерактивные code actions — рефакторинги, которые запрашивают у пользователя ввод перед применением: добавление/удаление тегов к полям структуры, реализация интерфейса.
📍 Навигация: Вакансии • Задачи • Собесы
#GoLive
Please open Telegram to view this post
VIEW IN TELEGRAM
Горутина утекает, когда блокируется на канале или локе, который никто никогда не освободит, и висит до конца жизни процесса. В Go 1.27 для таких случаев в
runtime/pprof добавляют профиль goroutineleak. Предложение приняли ещё в апреле, родом оно из Uber, оттуда же, откуда и популярная
uber-go/goleak.Главная идея в том, что детектор ездит на сборщике мусора. Горутина считается утёкшей, если канал или лок, на котором она стоит, недостижим ни из одной живой горутины, ни из тех, что живые могли бы разбудить. Разбудить её нечем, и GC помечает её как утечку. Отсюда же её главное отличие от
goleak. Профиль строит доказательство достижимости и работает на живом процессе без ложных срабатываний, тогда как
goleak делает снимок на завершении теста и на проде путает реальные утечки с горутинами, которые просто ждут следующего запроса.API крошечный. Никаких новых типов и функций, только профиль с именем
goroutineleak, который читается обычными инструментами pprof. Снять его можно четырьмя привычными способами:
• прямо из кода через
pprof.Lookup("goroutineleak").WriteTo(...)• в тесте по аналогии с
VerifyNone • по HTTP через
net/http/pprof• через
go tool pprof. Пока что профиль за флагом сборки, запускать нужно с
GOEXPERIMENT=goroutineleakprofile, в релизе 1.27 флаг уберут.Из-за правила «никаких ложных срабатываний» GC не трогает горутину, если её канал или лок всё ещё достижим. Но глобальная переменная или локали живой горутины могут держать этот канал достижимым ещё долго после того, как к нему реально кто-то обратится в последний раз. Такая горутина останется непомеченной. Всё, что профиль показывает, это настоящие утечки. Просто часть настоящих утечек он не покажет.
📍 Навигация: Вакансии • Задачи • Собесы
#GoLive
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7❤3
Go позволяет объявлять каналы только для отправки (
chan<- Job) или только для приёма (<-chan Job). В большинстве кодовых баз это не используют, функции просто принимают chan Job.Формально это допустимо, но вы теряете кое-что важное. Компилятор больше не может подсказать вам, что поток данных идёт неправильно.
Вот пул, где путаница с направлением приводит к багу:
func spawnWorkers(jobs chan Job, results chan Result) {
for i := 0; i < 10; i++ {
go func() {
for job := range jobs {
result := process(job)
jobs <- result.nextJob // воркер пишет обратно во входной канал
results <- result
}
}()
}
}Воркер случайно записал данные в
jobs вместо results. С двунаправленными каналами это компилируется без жалоб, и баг живёт в проде. С направленными типами падает уже на компиляции:func spawnWorkers(jobs <-chan Job, results chan<- Result) {
for i := 0; i < 10; i++ {
go func() {
for job := range jobs {
result := process(job)
jobs <- result.nextJob // ошибка компиляции: нельзя писать в канал только для приёма
results <- result
}
}()
}
}Направленные типы работают как документация, которую проверяет компилятор. Продюсер видит только
chan<- Job. Консьюмер видит только <-chan Job. Ни один не сможет сделать что-то не то по ошибке.Полный воркер-пул с правильным применением этого приёма:
func produce(jobs chan<- Job, jobList []Job) {
defer close(jobs)
for _, job := range jobList {
jobs <- job
}
}
func consume(jobs <-chan Job, results chan<- Result, wg *sync.WaitGroup) {
defer wg.Done()
for job := range jobs {
results <- process(job)
}
}
func run(jobList []Job) []Result {
jobs := make(chan Job, 100)
results := make(chan Result, 100)
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go consume(jobs, results, &wg)
}
go produce(jobs, jobList)
// Закрываем results, когда все воркеры закончили
go func() {
wg.Wait()
close(results)
}()
var out []Result
for result := range results {
out = append(out, result)
}
return out
}Каждая функция теперь видит только то, что ей положено. Компилятор следит за потоком данных. Читается всё легко.
produce подаёт задачи, consume их разгребает, отдельная горутина закрывает results, когда все воркеры завершились.Указывайте направление каналов на границах функций через
chan<- и <-chan. Это бесплатная документация, которую вдобавок проверяет компилятор. Тут даже тест не нужен, всё ловится при сборке. Привычка простая, а целый класс багов с записью не в ту сторону исчезает сам собой.📍 Навигация: Вакансии • Задачи • Собесы
#GoToProduction
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
В 1992 году Уорд Каннингем, создатель вики, описал кривой код через метафору займа. Быстрое решение сегодня ускоряет разработку. Но это кредит, и по нему капают проценты. Проценты это время, которое вы потом потратите на правки, баги и сопровождение.
Причин обычно несколько:
• Давление бизнеса. Бизнесу нужно выпустить продукт раньше, чем будут сделаны все необходимые изменения. В спешке накапливается долг.
• Отсутствие процессов или понимания. Бизнес не знает про технический долг и принимает решения, не учитывая последствий.
• Сильное зацепление компонентов. Систему разбили на части неправильно или недостаточно гибко, и она не адаптируется под изменения бизнес-потребностей.
• Отсутствие тестов. Ускоренная разработка и быстрые рискованные костыли для исправления ошибок.
• Отсутствие документации. Код пишут без сопроводительной документации. Работа по её созданию это тоже долг, который придётся погашать.
• Отсутствие взаимодействия между командами. Неэффективное управление знаниями в организации, например когда в команде нет наставничества.
• Отложенный рефакторинг. Чем дольше его откладывают и чем больше кода написано поверх текущего состояния, тем больше долг, который придётся погасить при следующем рефакторинге.
Технический долг это не ругательство и не всегда признак плохой команды. Это способ описать, во что обойдётся быстрое решение, принятое сегодня. Долг бывает осознанным и оправданным, а бывает случайным и опасным.
📍 Навигация: Вакансии • Задачи • Собесы
#GoDeep
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3🔥2
Знакомая команда
tree рисует дерево из директорий. А что если так же наглядно хочется посмотреть на структуру конфига или ответа API? Для этого есть xtree — небольшая CLI-утилита из проекта gtree, которая берёт JSON, YAML или TOML и выводит их в виде привычного ASCII-дерева.Вложенные конфиги и API-ответы быстро становятся нечитаемыми: глаза теряются в скобках и отступах.
xtree сводит любую из трёх структур к единому древовидному виду, где иерархия видна с первого взгляда. Удобно, когда нужно быстро понять форму данных, сравнить два файла или показать структуру коллеге.Это часть более крупного проекта gtree, который умеет строить деревья из Go-кода и из Markdown-списков.
xtree — отдельная команда именно для форматов данных.Базовое использование
Отдаёте файл в
stdin и вызываете xtree output:$ cat a.json | xtree output
$ cat a.yaml | xtree output
$ cat a.toml | xtree output
Возьмём JSON:
{
"name": "Alice",
"age": 30,
"roles": ["admin", "editor"],
"settings": {
"theme": "dark",
"notifications": true
},
"devices": [
{ "type": "mobile", "os": "ios" },
{ "type": "desktop", "os": "windows" }
]
}После
cat a.json | xtree output:.
├── age
│ └── 30
├── devices
│ ├── [0]
│ │ ├── os
│ │ │ └── ios
│ │ └── type
│ │ └── mobile
│ └── [1]
│ ├── os
│ │ └── windows
│ └── type
│ └── desktop
├── name
│ └── Alice
├── roles
│ ├── [0]
│ │ └── admin
│ └── [1]
│ └── editor
└── settings
├── notifications
│ └── true
└── theme
└── dark
Та же логика работает для YAML и TOML — на выходе получаем идентичное дерево. Элементы массивов помечаются индексами
[0], [1] и так далее.У команды
output есть три флага, которые делают вывод лучше.•
--omit-index (-o) убирает индексы массивов. Вместо отдельных веток [0], [1] значения схлопываются под общим ключом:$ cat a.json | xtree output --omit-index
...
├── roles
│ ├── admin
│ └── editor
...
•
--level N (-l) ограничивает глубину дерева. Удобно для обзора верхнего уровня большого файла:$ cat a.json | xtree output --level 1
.
├── age
├── devices
├── height
├── is_active
├── metadata
├── name
├── roles
└── settings
•
--allow-duplicate (-a) разрешает узлы с одинаковыми именами на одном уровне.Флаги комбинируются — например,
xtree output --level 2 --omit-index покажет два уровня без индексов.xtree — это маленький, но удобный инструмент на каждый день: один пайп, и нечитаемый конфиг превращается в наглядное дерево. 📍 Навигация: Вакансии • Задачи • Собесы
#GoToProduction
Please open Telegram to view this post
VIEW IN TELEGRAM
👍10🤔1
⏰ Уже сегодня в 19:00 (МСК) стартует наш открытый урок!
Знаем, летом хочется отложить учёбу на осень. Но пока остальные отдыхают 😅, мы предлагаем без лишней конкуренции прокачать свой workflow и начать закрывать тикеты в три раза быстрее.
Тема:
Мы покажем живой разбор реального проекта: как с помощью AI-ассистентов мгновенно разбираться в чужой кодовой базе, искать нужные участки и собирать Pull Request. Только рабочие промпты, которые сэкономят вам часы рутины.
🎙 Спикер — Ольга Лукьянова, руководитель команды в SourceCraft (18+ лет развивала инструменты в JetBrains и Huawei).
👉 Узнать, как ускорить разработку с AI
Знаем, летом хочется отложить учёбу на осень. Но пока остальные отдыхают 😅, мы предлагаем без лишней конкуренции прокачать свой workflow и начать закрывать тикеты в три раза быстрее.
Тема:
«AI-инструменты в разработке: пишем код быстрее»
Мы покажем живой разбор реального проекта: как с помощью AI-ассистентов мгновенно разбираться в чужой кодовой базе, искать нужные участки и собирать Pull Request. Только рабочие промпты, которые сэкономят вам часы рутины.
🎙 Спикер — Ольга Лукьянова, руководитель команды в SourceCraft (18+ лет развивала инструменты в JetBrains и Huawei).
👉 Узнать, как ускорить разработку с AI
🧑💻 Утечка горутин из-за каналов, которые никто не закрывает
Go делает конкурентность простой. Горутины дешёвые, каналы встроены в язык, а воркер-пул собирается меньше чем за 30 строк. Но из-за этой простоты в код проникают баги. Программа компилируется, тесты проходят, а ошибка всплывает только под нагрузкой или после нескольких часов работы. Одна из частых ошибок в воркер-пулах это утечка горутин.
❓ В чём проблема
Вот воркер-пул, который выглядит вполне разумно:
Каждый воркер читает канал
Цикл
В долгоживущем сервисе это накапливается. Каждый раз, когда вы перезапускаете пул, например при перезагрузке конфига или новой пачке задач, не дренируя старый, вы добавляете утёкшие горутины.
➡️ Как чинить
Продюсер закрывает канал, когда закончил, и эта ответственность лежит только на нём.
Здесь два важных момента:
• во-первых,
• во-вторых,
Продюсер владеет каналом и закрывает его, всегда через
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека 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().📍 Навигация: Вакансии • Задачи • Собесы
#GoToProduction
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8🔥2👏1
Glaze теперь полностью CGo-free. Это десктопный WebView-тулкит для Go, который позволяет собирать нативные окна с интерфейсом на HTML/CSS/JS — и при этом обходится без C-тулчейна в сборке.
Glaze вырос из проекта go-webview, но со временем заметно разошёлся с оригиналом и развивается как отдельная кодовая база со своими целями и API.
Обычно привязки к нативным 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-приложение как десктоп — выглядит очень аккуратно и практично.📍 Навигация: Вакансии • Задачи • Собесы
#GoToProduction
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7❤2
🐞
В трекере Go появился любопытный баг: новый стандартный пакет
🌸 Как повторить
Вызвать
Обратите внимание на третий блок — он всегда
❓ Почему 7000 — это проблема
Чтобы понять, что именно сломано, полезно вспомнить структуру UUID версии 7. Внутри 128 бит лежат:
В строковом виде
Страдает именно 12-битное поле
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека Go-разработчика
#GoLive
uuid.NewV7() теряет случайность в браузереВ трекере Go появился любопытный баг: новый стандартный пакет
uuid, который должен приехать в Go 1.27, на таргете js/wasm генерирует «бракованные» UUIDv7 — часть случайных бит всегда оказывается нулевой.Вызвать
uuid.NewV7().String() и собрать программу под js/wasm (например, GOOS=js GOARCH=wasm go run main.go через Node). В результате на свет стабильно появляются значения такого вида:019ee60f-29b3-7000-a12b-f817e25db8f4
019ee610-29c7-7000-bc34-f04bc09150bb
019ee610-2eb4-7000-884a-dfcad78e47d9
Обратите внимание на третий блок — он всегда
7000. Меняется только timestamp в начале и хвост, а вот этот фрагмент будто прибит гвоздями.Чтобы понять, что именно сломано, полезно вспомнить структуру 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 что-то идёт не так.📍 Навигация: Вакансии • Задачи • Собесы
#GoLive
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥6🥱4❤2😢1
История из боевых распределённых систем. Один кривой подписчик уронил доставку всем остальным, потому что паника в горутине без
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-хендлера на каждую горутину, которую вы заводите сами.📍 Навигация: Вакансии • Задачи • Собесы
#GoToProduction
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6❤2
Backend разработчик — от 450 000 ₽, удаленно.
Руководитель группы разработки — офис в Москве.
Go-разработчик — до 485 000 ₽, удаленно.
Бустер — удалённо (не только Москва)
#GoWork
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔1
🔄 gcli v3.8.0, общие опции и генерация документации
🌻 Общие опции для подкоманд
Раньше, если один и тот же флаг нужен нескольким подкомандам, его приходилось объявлять в каждой заново.
В 3.8.0 появилась трёхслойная модель опций. Через
Слияние идемпотентно, локальная опция перекрывает общую, а проверка
Рядом добавили примитив
🌻 Генерация документации из команд
Второе крупное изменение это пакет
Встроенная команда
🌻 Рефакторинг и фиксы
Внутри вынесли
➡️ Релиз
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека Go-разработчика
#GoLive
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.📍 Навигация: Вакансии • Задачи • Собесы
#GoLive
Please open Telegram to view this post
VIEW IN TELEGRAM
❤5
👀 Умеете строить предсказуемую архитектуру с ИИ? Станьте спикером 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
Мы в Proglib.academy запускаем курс по Spec-Driven Development (SDD) — учим разработчиков управлять ИИ-агентами (Cursor, Copilot, Claude) через спецификации, контекст и тесты, чтобы не перепроверять за нейросетью каждую строку.
Для этого курса мы ищем классных спикеров-практиков.
● BigTech бэкграунд: опыт работы разработчиком, тимлидом или архитектором в крупных технологических компаниях.
● Опыт управляемой разработки с ИИ: вы на практике знаете, как встроить AI-инструменты в личный или командный workflow (от постановки задачи до ревью AI-кода).
● Системный подход: умение превращать хаотичную генерацию кода в предсказуемый инженерный процесс (работа с архитектурой, чек-листами, легаси и покрытием тестами).
● Достойную оплату за подготовку материалов и проведение занятий.
● Мощное продвижение личного бренда через медиаресурсы Proglib (наша аудитория — 1 млн+ айтишников).
● Возможность публиковать свои экспертные материалы и статьи на наших площадках.
● Доступ к сильному и закрытому профессиональному сообществу.
Также мы ищем консультанта программы.
Если вы практикующий эксперт и готовы помочь нам отвалидировать программу курса, дать рекомендации по актуальности тем и финальным результатам обучения — мы вас очень ждем.
Telegram: @alinaa_kh
E-mail: alina@proglib.io
Please open Telegram to view this post
VIEW IN TELEGRAM
1
Когда вы пишете микросервис на 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 экономия времени ощутимая.
📍 Навигация: Вакансии • Задачи • Собесы
#GoToProduction
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥11❤3👍3🥱1
В трекере 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.
📍 Навигация: Вакансии • Задачи • Собесы
#GoLive
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2❤1
🤖 Запускаем новый курс: «Spec-Driven Development»!
Всем надоело, что ИИ-агенты (Cursor, Claude) генерируют сотни строк хаотичного кода, который приходится переписывать. Дело не в нейросети, а в том, что вы заставляете её угадывать условия задачи.
На курсе вы освоите методологию SDD (Spec-Driven Development) — управление ИИ через спецификации (спеки) и тесты вместо «вайб-кодинга».
Как это работает?
До генерации кода вы фиксируете в спеке контракты, инварианты и граничные случаи. Документ четко определяет ЧТО делать, сводя лотерею в PR к нулю.
За 8 недель с экспертами из BigTech вы:
🔹 Встроите ИИ в личный или командный workflow.
🔹 Превратите генерацию кода в предсказуемый инженерный процесс.
🔹 Перестанете перепроверять за моделями каждую строчку.
📖 Полный разбор методологии с примерами и готовый промпт для генерации спеки — в нашей статье
👉 Освоить SDD и ускорить разработку
Всем надоело, что ИИ-агенты (Cursor, Claude) генерируют сотни строк хаотичного кода, который приходится переписывать. Дело не в нейросети, а в том, что вы заставляете её угадывать условия задачи.
На курсе вы освоите методологию SDD (Spec-Driven Development) — управление ИИ через спецификации (спеки) и тесты вместо «вайб-кодинга».
Как это работает?
До генерации кода вы фиксируете в спеке контракты, инварианты и граничные случаи. Документ четко определяет ЧТО делать, сводя лотерею в PR к нулю.
За 8 недель с экспертами из BigTech вы:
🔹 Встроите ИИ в личный или командный workflow.
🔹 Превратите генерацию кода в предсказуемый инженерный процесс.
🔹 Перестанете перепроверять за моделями каждую строчку.
📖 Полный разбор методологии с примерами и готовый промпт для генерации спеки — в нашей статье
👉 Освоить SDD и ускорить разработку
❤1👏1
Вот простой код:
func foo() {}Тело есть. Но оно пустое. Функция ничего не делает, ничего не возвращает.
Go — язык строгий. Он не любит неиспользуемые переменные и лишний импорт. Но как он относится к функции, которая существует и при этом ничего не делает?
Подсказка:
📍 Навигация: Вакансии • Задачи • Собесы
#ReadySetGo
Please open Telegram to view this post
VIEW IN TELEGRAM
🐠 Открытый фреймворк для учебного фишинга
Gophish это опенсорсный инструмент для проведения фишинговых учений и обучения сотрудников. Он рассчитан на бизнес и пентестеров, которым нужно легально проверить, как команда реагирует на поддельные письма. Проект написан на Go.
Запускать такие кампании можно только против своей организации или там, где у вас есть письменное разрешение. Без него это уже не учения.
Вы описываете кампанию в веб-интерфейсе, а инструмент сам рассылает письма, поднимает посадочные страницы, отслеживает открытия и переходы и собирает всё в отчёты.
Вся функциональность доступна и через REST API, поэтому кампании можно гонять из скриптов и встраивать в свои пайплайны отчётности.
➡️ Как запустить
Самый быстрый путь это готовый бинарь. Под Windows, macOS и Linux есть релизы, их достаточно скачать, распаковать и запустить. Или собрать самостоятельно:
Есть и официальный Docker образ, если не хотите ставить ничего на хост:
После старта админка поднимается на
Gophish превращает разрозненную ручную работу по фишинговым учениям в управляемый процесс с нормальной статистикой.
➡️ Репозиторий
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека Go-разработчика
#GoToProduction
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 превращает разрозненную ручную работу по фишинговым учениям в управляемый процесс с нормальной статистикой.
📍 Навигация: Вакансии • Задачи • Собесы
#GoToProduction
Please open Telegram to view this post
VIEW IN TELEGRAM
❤5🤔4🌚3