Раздел 22 · Go

Отладка кода: delve, VS Code и грязный print

junior~50 мин

без входаВойди, чтобы отмечать прогресс, вести заметки и решать задачи в редакторе. войти

Перед этим уроком

Отладка кода: delve, VS Code и грязный print

Существует одна стыдная истина: подавляющее большинство Go-программистов отлаживается fmt.Println. Это работает, но в 4 случаях из 5 это дольше и грязнее, чем поставить breakpoint и посмотреть состояние напрямую. На junior-уровне нормально начинать с print-debug, но к концу этого урока у тебя на ноуте должен быть dlv, на котором ты прошёл одну реальную сессию.

Дальше в 06-functions будут функции, и дебаггер пригодится: войти в функцию по step-in проще, чем читать стек.

1. Три уровня отладки

УровеньИнструментКогда
Грубыйfmt.Println, log.Printf, log/slogбыстро глянуть значение раз
Структурныйlog/slog с уровнями и атрибутамив боевом коде, чтобы не выпиливать перед коммитом
Полныйdlv (delve) или дебаггер в IDEсложный баг, состояние с десятком переменных, паника в чужом коде

Идея простая: чем серьёзнее баг, тем выше по уровням лезешь. И обратно: если три Println решают вопрос, не надо ставить дебаггер.

2. Грязный print, по-нормальному

fmt.Printf("DBG: user=%+v err=%v\n", user, err)

Три быстрых правила:

  • Префикс DBG: или похожий, чтобы потом найти и удалить grep-ом.
  • %+v для структур, иначе видишь {1 hi} без названий полей.
  • Печатать в os.Stderr через fmt.Fprintln(os.Stderr, ...), если вывод программы (stdout) важен и не должен мусориться. Например, для CLI которая читается пайпом.

Это нормально для разовых проверок. Но как только баг повторяется чаще одного раза, пора в дебаггер.

log/slog когда не разовое

Когда логи нужны не на 5 минут, а на месяцы (в проде или в CI), бери log/slog, он в stdlib с Go 1.21:

import "log/slog"

slog.Info("user fetched", "id", user.ID, "took", elapsed)
slog.Error("fetch failed", "url", url, "err", err)

Преимущество: можно настроить JSON-вывод одной строкой, поставить уровень debug, и забыть. Подробнее в 15-idioms-and-stdlib §5.

3. Установка delve

Delve это дебаггер уровня gdb, но заточенный под Go. Без него VS Code и GoLand не умеют отлаживать.

go install github.com/go-delve/delve/cmd/dlv@latest
dlv version
# Delve Debugger
# Version: 1.24.x

Проверь, что бинарь попал в $PATH:

which dlv
# /Users/you/go/bin/dlv

Если “command not found”, допиши $(go env GOPATH)/bin в $PATH (см. урок 00).

На macOS первый запуск может попросить подписать бинарь. Это нормально:

codesign -s - -f --entitlements <(cat <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
  <key>com.apple.security.cs.allow-jit</key><true/>
  <key>com.apple.security.cs.debugger</key><true/>
</dict></plist>
EOF
) $(which dlv)

VS Code и GoLand сделают это сами через свои Go-плагины.

4. Пять команд delve, которых хватит на 95%

Запуск:

dlv debug                       # текущий main-пакет
dlv debug ./cmd/avg             # конкретный
dlv test ./internal/strbench    # отлаживать тесты
dlv attach <pid>                # подключиться к запущенному процессу

Ты попадаешь в REPL (dlv). Команды (полное имя и сокращение):

КомандаСокращениеЧто делает
break <file>:<line>bпоставить breakpoint
continuecбежать до следующего breakpoint
nextnследующая строка, не заходя в функции
stepsследующая строка, заходя в функцию
print <expr>pнапечатать значение выражения
localsloвсе локальные переменные
argsаргументы текущей функции
goroutinesgrsсписок горутин
goroutine <id>grпереключиться на горутину
stackbtстек вызовов
restartrперезапустить программу
quitqвыйти

Минимальная сессия:

$ dlv debug ./cmd/avg
Type 'help' for list of commands.
(dlv) break main.main
Breakpoint 1 set at 0x... for main.main()
(dlv) continue
> main.main() ./main.go:12 (hits goroutine(1):1 total:1)
(dlv) next
> main.main() ./main.go:13
(dlv) print sum
0
(dlv) locals
sum = 0
count = 0
scanner = bufio.Scanner{...}
(dlv) continue

То есть: поставил breakpoint на функцию, добежал, прошёл одну строку, посмотрел переменные. Этого хватит на огромное количество багов.

5. Условные breakpoint и watchpoint

Иногда строка выполняется тысячу раз, и интересна одна конкретная. Условный breakpoint:

(dlv) break main.go:42 cond i == 999

Прерывание сработает только когда i == 999. Удобно, когда баг вылезает на тысячной итерации.

watch <expr> останавливает выполнение, когда значение выражения меняется. Полезно ловить, когда какое-то поле затирают:

(dlv) watch -w user.Active

премиум-урок

Продолжение в премиуме: 1 200 ₽ в месяц или 10 000 ₽ в год. Вместе с ним серверные проверки задач и школьные токены AI-коуча.

Оплата пока не подключена: премиум и курсы открываются промокодом.