Отладка Go кода: от новичка до профи за один вечер

Онлайн-тренажер Python для начинающих

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

Начать курс

Почему ваш Go-код падает, и как это исправить без головной боли

Вы написали идеальный, по вашему мнению, код на Go. Компиляция прошла на ура, тесты зелёные... но на проде всё летит в тартарары. Знакомо? Добро пожаловать в реальный мир разработки, где баги — это не ошибки новичков, а часть профессионального роста. Сегодня мы разберём отладку Go-кода так, чтобы вы не просто находили ошибки, а предотвращали их появление на этапе написания.

Go — язык, который даёт разработчику мощные инструменты для отладки прямо «из коробки». Но многие используют только fmt.Println и искренне удивляются, почему их горутины ведут себя как кошки на раскалённой крыше. Давайте исправим это раз и навсегда.

Техника №1: Умный Print — когда log.Fatal не нужен

Да, я знаю, что вы скажете: «Зачем мне delve, если я могу напечатать переменную?» И будете правы в 80% случаев. Но проблема не в fmt.Println, а в том, как вы его используете.

Переходим от Println к structured logging

Вместо того чтобы лепить десятки принтов, используйте пакет log или slog (доступен с Go 1.21):

package main

import (
    "log/slog"
    "os"
)

func main() {
    logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
    
    // Вместо fmt.Println("ошибка:", err)
    if err := doSomething(); err != nil {
        logger.Error("операция не удалась", 
            "error", err,
            "module", "data_processor",
            "attempt", 3,
        )
    }
}

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

Лайфхак: условная компиляция для debug-логов

Чтобы не засорять продакшн, используйте build tags:

//go:build debug

package main

var debug = true
//go:build !debug

package main

var debug = false

Запускаете с флагом -tags debug — получаете подробные логи. Без флага — чистый продакшн.

Техника №2: Delve — ваш личный детектив для Go

Если print-отладка — это велосипед, то Delve — это Tesla. Этот отладчик создан специально для Go и понимает горутины, интерфейсы и даже generics. Установка простая:

go install github.com/go-delve/delve/cmd/dlv@latest

Точки остановки, которые умеют думать

Delve позволяет ставить не просто breakpoint, а условные точки:

(dlv) break main.go:25 if userID == 42
(dlv) continue

Отладчик остановится только когда userID будет равен 42. Представьте, сколько времени это экономит при отладке циклов.

Исследование горутин

Самое страшное в Go — deadlock'и и race condition. Delve показывает все активные горутины:

(dlv) goroutines
  Goroutine 1 - User: ./main.go:10 main.main (0x109a5b6)
  Goroutine 2 - User: ./main.go:15 main.worker (0x109a6c8)
* Goroutine 3 - User: ./main.go:22 main.worker (0x109a6c8)

Звёздочка показывает текущую горутину. Переключайтесь между ними:

(dlv) goroutine 2
(dlv) stack

Работа с памятью: смотрим, что внутри

Команда locals показывает все локальные переменные, а print выводит их значения с типами:

(dlv) locals
data = []int len: 100000, cap: 100000
result = map[string]int ["error": 5, "success": 99995]
(dlv) print data[0:5]
[]int len: 5, cap: 100000, [42, 17, 89, 3, 256]

Техника №3: Профилирование pprof — находим узкие места

Ваш код работает, но медленно? pprof — это встроенный профайлер Go. Он покажет, где программа «тормозит» и где ест память.

Добавляем HTTP-эндпоинт для профилирования

package main

import (
    "net/http"
    _ "net/http/pprof"
)

func main() {
    // Запускаем pprof на отдельном порту
    go func() {
        http.ListenAndServe(":6060", nil)
    }()
    
    // Ваш основной код
    select {}
}

Теперь открывайте в браузере http://localhost:6060/debug/pprof/ и смотрите:

  • goroutine — сколько горутин и где они ждут
  • heap — утечки памяти
  • profile — CPU-профиль (30 секунд сбора данных)

Анализ CPU-профиля из командной строки

# Собираем профиль
go tool pprof -http=:8080 http://localhost:6060/debug/pprof/profile?seconds=30

# Откроется веб-интерфейс с flame graph и графом вызовов

Если вы видите, что функция json.Unmarshal занимает 70% времени — это повод задуматься об оптимизации сериализации.

Техника №4: Tracing — как работает ваша программа на самом деле

go trace — это «рентген» вашего приложения. Он показывает не только какие функции вызываются, но и когда, и как долго выполняются горутины.

package main

import (
    "os"
    "runtime/trace"
)

func main() {
    f, _ := os.Create("trace.out")
    defer f.Close()
    
    trace.Start(f)
    defer trace.Stop()
    
    // Ваш код
}

После запуска анализируем:

go tool trace trace.out

Откроется браузер с интерактивным таймлайном. Вы увидите:

  • Когда горутины спят в ожидании каналов
  • Сколько времени занимает GC
  • Где происходят блокировки

Техника №5: Race detector — убийца гонок данных

Гонки данных (data races) — бич многопоточного программирования. Go встроил детектор прямо в компилятор:

go run -race main.go
go test -race ./...

Если есть гонка, вы получите чёткий отчёт:

==================
WARNING: DATA RACE
Write at 0                

Блоги

Рекомендаций по книгам