Как отвечать: определение → как работает внутри → где ловушка → пример. Отвечайте вслух до чтения ответа — собеседование это устный жанр, и «понимаю про себя» не равно «объясняю словами».
1. Почему в Go нет исключений? Как обрабатывают ошибки?
Ошибки — обычные значения: функция возвращает (результат, error), и вызывающий обязан проверять if err != nil. Решение осознанное: путь ошибки виден в сигнатуре и в коде, ничего не «прилетает через воздух», поток управления явный. Ценой становится многословность — сообщество считает её приемлемой платой за читаемость. Паники существуют, но для программных ошибок (выход за границы среза, nil-разыменование), а не для ожидаемых сбоев вроде «файл не найден».
data, err := os.ReadFile("conf.yaml")
if err != nil {
return fmt.Errorf("читаю конфиг: %w", err)
}2. Зачем оборачивать ошибки и что даёт %w?
Оборачивание сохраняет контекст («где сломалось») и цепочку причин: fmt.Errorf с глаголом %w возвращает ошибку-обёртку, внутри которой жива исходная. Распаковка — errors.Is (сравнение по цепочке с sentinel-ошибкой: errors.Is(err, io.EOF)) и errors.As (извлечение конкретного типа: var pe *fs.PathError; errors.As(err, &pe)). Без %w контекст есть, но связи нет — errors.Is вернёт false. Вопрос-ловушка: fmt.Errorf с %v вместо %w тоже «добавляет текст», но рвёт цепочку — на собеседовании это маркер непонимания.
if err != nil {
return fmt.Errorf("загружаю профиль %d: %w", userID, err)
}
// далеко наверху:
if errors.Is(err, sql.ErrNoRows) { /* профиль не найден */ }3. Что такое sentinel-ошибки и в чём их недостаток?
Sentinel — заранее объявленная переменная-ошибка (io.EOF, sql.ErrNoRows), с которой сравнивают через errors.Is. Плюс — простота и стабильный контракт. Минус — они глобальные и «все на них равняются»: разные пакеты могут вернуть один sentinel по разным поводам, и проверка станет слишком широкой. Эволюция подхода в Go: от sentinel к типизированным ошибкам (свой тип с полями — сравнение через errors.As) и к обёрткам с контекстом. В ответе стоит сказать: и то и то законно, выбор — между «одинаковая причина» (sentinel) и «нужны детали» (тип).
4. Что делает defer и когда в нём нужен recover?
recover имеет смысл ТОЛЬКО внутри отложенной функции: defer func() { if r := recover(); r != nil { … } }(). Он останавливает раскрутку паники и возвращает её значение — так пишут обёртки над чужим кодом (HTTP-хендлеры, воркеры очередей), чтобы паника в одной задаче не убивала процесс. recover вне defer бесполезен (вернёт nil). Важно проговорить границу: recover — для восстановления на границе модуля (обработать, залогировать, вернуть 500), а не для управления обычным потоком; ошибки — значениями, паника — аварией.
func safeWorker(job Job) {
defer func() {
if r := recover(); r != nil {
log.Printf("воркер упал: %v\n%s", r, debug.Stack())
}
}()
run(job) // паника здесь не убьёт процесс
}5. В каком порядке выполняются defer и return? Вопрос про именованный возврат.
return в Go — два шага: (1) присвоить возвращаемым переменным значения, (2) выполнить отложенные вызовы, (3) выйти. С именованными возвратами (func f() (err error)) defer может изменить результат: в отложенной функции err = fmt.Errorf(...) перезапишет то, что вернул return. Это база паттерна «defer меняет err»: повторно закрыть ресурс, добавить контекст через errors.Join. Ловушка: голый return с изменённым именованным err — читаемость падает, о такой модификации надо заявлять комментарием.
func read() (err error) {
defer func() {
if err != nil {
err = fmt.Errorf("read: %w", err) // добавили контекст
}
}()
_, err = os.ReadFile("x")
return
}6. Паника в горутине — что произойдёт? Как защитить сервис?
Паника, не восстановленная в горутине, убивает ВЕСЬ процесс — не только эту горутину. Стек-трейс печатается, программа падает. Для сервисов с горутинами-воркерами значит: каждая «чужая» точка входа (задача из очереди, хендлер, колбэк) получает defer-recover обёртку, которая логирует панику со стеком и продолжает работу. Стандартная HTTP-библиотека так делает для хендлеров, а для своих воркеров обёртку пишут сами или берут errgroup/пул с recover. Ответ стоит закончить: recover — страховка на границе, а не замена обработке ошибок внутри.
7. Когда паниковать самому?
Короткий список. Невозможное состояние, которое означает ошибку программиста: переключатель дошёл до невалидной ветки, инвариант структуры нарушен после проверки. Инициализация: обязательный конфиг не задан — паника в init/конструкторе честнее, чем работать с молча неверными данными. В публичных API библиотек — почти никогда: возвращайте ошибки, решение за вызывающим. Запрещённые кейсы: валидация пользовательского ввода (это ошибка, не паника), ретраебельные сбои сети/базы.
Прокачать тему в самоучителе
Читается быстро, запоминается практикой. Эти курсы закрывают тему с запуском кода прямо на странице:
- Язык Go с нуля: базовый синтаксис и конструкции — Интерактивный курс базового синтаксиса Go для начинающих: переменные, циклы, срезы, функции, структуры. 25 уроков с кодом в браузере, тестами и первым приложением.
- Продвинутые возможности языка Go — Продвинутый курс Go: замыкания, defer, горутины, каналы, контекст, интерфейсы, дженерики, рефлексия, тесты и JSON. 25 уроков с практикой в браузере.