12 ChatGPT Prompts That Turn Go Concurrency, gRPC, and pprof Into a Senior Engineer's Playbook

Go без сюрпризов: 12 промтов для генерации конкурентного кода, gRPC-сервисов и оптимизации горутин

Go создавался в Google для сетевых сервисов, и его модель конкурентности — каналы и горутины — стала визитной карточкой языка. Но именно здесь прячутся самые дорогие баги: deadlock'и, race conditions, утечки горутин и context canceled, которые всплывают только под нагрузкой. По данным опроса Go Developer Survey (go.dev/blog/survey2023-h2-results), разработчики регулярно называют конкурентность одной из самых сложных тем языка.

Хорошая новость: LLM уже неплохо знает идиомы Go. Плохая — без точного промта она выдаёт «работающий» код, который падает на ревью или в продакшене. Ниже — 12 промтов, которые я использую каждый день: от каналов и errgroup до pprof-профилирования и бенчмарков. Каждый промт — с задачей, готовым текстом и пояснением, когда его применять.

1. Идиоматичный конкурентный код с каналами и sync

Когда применять: нужен воркер-пул, fan-out/fan-in или pipeline без утечек горутин.

Промт:

Ты — senior Go-разработчик. Сгенерируй идиоматичный код для задачи: [опиши задачу]. Требования: используй context.Context для отмены, sync.WaitGroup для ожидания, каналы буферизованные там, где это оправдано. Закрывай каналы только со стороны отправителя. Объясни, где может возникнуть deadlock и как ты его избежал.

Пример: воркер-пул на 5 горутин, читающий URL из канала и пишущий результат в results.

func workerPool(ctx context.Context, urls <-chan string, workers int) <-chan string {
    results := make(chan string, workers)
    var wg sync.WaitGroup
    for i := 0; i < workers; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            for url := range urls {
                select {
                case <-ctx.Done():
                    return
                case results <- fetch(url):
                }
            }
        }()
    }
    go func() { wg.Wait(); close(results) }()
    return results
}

2. Ревью на race conditions и утечки горутин

Когда применять: перед мержем кода, который трогает общее состояние.

Промт:

Проведи ревью кода ниже на предмет race conditions, утечек горутин и deadlock'ов. Для каждой проблемы укажи строку, объясни механизм и предложи минимальный фикс. Учти правила go vet, -race и рекомендации из Go Memory Model (go.dev/ref/mem).

Пример проблемы: горутина пишет в map без мьютекса — go test -race это поймает, но ревью быстрее.

// ПЛОХО: concurrent map writes
var m = map[string]int{}
for _, v := range vals { go func(){ m[v]++ }() }

// ХОРОШО:
var mu sync.Mutex
for _, v := range vals {
    go func(v string){ mu.Lock(); m[v]++; mu.Unlock() }(v)
}

Всегда передавай переменную цикла параметром — в Go до 1.22 замыкание захватывало её по ссылке (см. release notes Go 1.22, go.dev/doc/go1.22).

3. gRPC-сервис из proto-файла

Когда применять: нужен boilerplate для нового сервиса.

Промт:

По .proto ниже сгенерируй полный gRPC-сервер на Go: реализацию сервиса, interceptor'ы для логирования и recovery, graceful shutdown через grpc.GracefulStop(), а также клиент с таймаутами. Используй google.golang.org/grpc и protoc-gen-go-grpc. Не забудь про context в каждом методе.

Пример: сервис UserService с методом GetUser.

func (s *server) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.User, error) {
    ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
    defer cancel()
    u, err := s.db.Find(ctx, req.Id)
    if err != nil { return nil, status.Errorf(codes.NotFound, "user %s: %v", req.Id, err) }
    return u, nil
}

4. Оптимизация горячего пути по pprof

Когда применять: сервис жрёт CPU или память, но непонятно где.

Промт:

У меня есть вывод go tool pprof -top (вставь). Объясни топ-5 функций по flat/cum, предложи конкретные оптимизации: аллокации, sync.Pool, замена структур, уменьшение escape-анализа. Для каждой гипотезы дай код до/после и способ проверить через -benchmem.

Пример: fmt.Sprintf в горячем пути заменяем на strconv + strings.Builder — типичный выигрыш по аллокациям.

5. Бенчмарки и таблица результатов

Когда применять: нужно доказать, что оптимизация работает.

Промт:

Напиши бенчмарки Go для функции [X] в стиле table-driven. Используй b.ReportAllocs(), b.ResetTimer(), sub-benchmarks через b.Run. Сравни минимум 3 реализации и объясни, как читать ns/op, B/op, allocs/op.

func BenchmarkParse(b *testing.B) {
    for _, impl := range []struct{name string; fn func(string) int}{
        {"naive", parseNaive}, {"optimized", parseOpt},
    } {
        b.Run(impl.name, func(b *testing.B) {
            b.ReportAllocs()
            for i := 0; i < b.N; i++ { impl.fn(sample) }
        })
    }
}

6. errgroup вместо ручного WaitGroup

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

Промт:

Перепиши код с sync.WaitGroup на golang.org/x/sync/errgroup с errgroup.WithContext. Покажи отмену остальных горутин при первой ошибке и установку SetLimit.

g, ctx := errgroup.WithContext(ctx)
g.SetLimit(10)
for _, id := range ids {
    id := id
    g.Go(func() error { return process(ctx, id) })
}
return g.Wait()

7. Worker pool с backpressure

Когда применять: неконтролируемый рост очереди задач.

Промт:

Сгенерируй worker pool с ограниченным буфером канала и backpressure: если очередь заполнена, продюсер блокируется или отбрасывает задачи с метрикой. Объясни trade-off между блокировкой и drop.

8. Graceful shutdown HTTP-сервера

Когда применять: деплой без обрыва соединений.

Промт:

Напиши main с graceful shutdown: signal.NotifyContext, http.Server.Shutdown с таймаутом, ожидание активных запросов, закрытие БД. Учти порядок остановки.

ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
<-ctx.Done()
shCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
_ = srv.Shutdown(shCtx)

9. Пайплайны и select с default

Когда применять: нужно неблокирующее чтение или таймауты.

Промт:

Объясни и покажи паттерны select: неблокирующий default, time.After для таймаута, приоритетный select через вложенный. Покажи, почему time.After в цикле течёт — замени на time.NewTimer.

10. Контекст: отмена, дедлайны, значения

Когда применять: везде, где есть I/O.

Промт:

Покажи правила работы с context: где хранить, а где не хранить значения, почему не передавать context.Background() вглубь, как правильно деривировать дедлайны. Приведи антипаттерны.

11. Тесты с -race и t.Parallel

Когда применять: конкурентный код без покрытия.

Промт:

Напиши тесты для функции [X], включая конкурентный кейс с t.Parallel(). Покажи, как запускать go test -race -count=10 и почему -count важен для флаки-тестов.

12. Генерация кода: go generate и stringer

Когда применять: повторяющийся boilerplate.

Промт:

Покажи, как настроить go generate с stringer и mockgen для проекта. Приведи директиву в коде, Makefile-цель и объясни порядок в CI.

//go:generate stringer -type=Status
type Status int

Что дальше

Эти 12 промтов покрывают 80% ежедневной работы Go-разработчика: конкурентность, gRPC, профилирование и тесты. Главное правило — всегда просить модель обосновать решение и показать, как проверить результат (-race, pprof, -benchmem). LLM ускоряет черновик, но ответственность за deadlock остаётся на вас.

Начните с промта №1 и №2 на текущем проекте — и прогоните go test -race ./.... Если найдёте баг, который модель помогла поймать, — считайте, что подборка уже окупилась.

← All posts

Comments