---
title: "Ошибки, паники и recover на собеседовании Go: вопросы с ответами"
description: "Вопросы собеседования Go про ошибки: if err != nil, оборачивание %w, errors.Is и As, sentinel-ошибки, defer и recover, когда паника уместна — с кодом."
url: "https://golangmaster.ru/interview/oshibki-i-paniki/"
published: "2026-09-19"
updated: "2026-09-21"
level: "junior"
questions: 7
---

# Ошибки, паники и recover на собеседовании Go: вопросы с ответами

*Уровень: junior. ~16 минут, 7 вопросов. Источник: https://golangmaster.ru/interview/oshibki-i-paniki/*

Обработка ошибок — то, что проверяют почти первым: идиома if err != nil, оборачивание, разбор ошибок через errors.Is/As и границы паники. Ответы ниже закрывают стандартный набор вопросов; полный путь темы — [урок про ошибки в базовом курсе](/courses/go-basics/lessons/21-oshibki-i-vozvraty/) и [паника с recover в продвинутом](/courses/go-advanced/lessons/03-panic-recover/).

## Почему в Go нет исключений? Как обрабатывают ошибки?

Ошибки — обычные значения: функция возвращает (результат, error), и вызывающий обязан проверять if err != nil. Решение осознанное: путь ошибки виден в сигнатуре и в коде, ничего не «прилетает через воздух», поток управления явный. Ценой становится многословность — сообщество считает её приемлемой платой за читаемость. Паники существуют, но для программных ошибок (выход за границы среза, nil-разыменование), а не для ожидаемых сбоев вроде «файл не найден».

```go
data, err := os.ReadFile("conf.yaml")
if err != nil {
    return fmt.Errorf("читаю конфиг: %w", err)
}
```

## Зачем оборачивать ошибки и что даёт %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 тоже «добавляет текст», но рвёт цепочку — на собеседовании это маркер непонимания.

```go
if err != nil {
    return fmt.Errorf("загружаю профиль %d: %w", userID, err)
}
// далеко наверху:
if errors.Is(err, sql.ErrNoRows) { /* профиль не найден */ }
```

## Что такое sentinel-ошибки и в чём их недостаток?

Sentinel — заранее объявленная переменная-ошибка (io.EOF, sql.ErrNoRows), с которой сравнивают через errors.Is. Плюс — простота и стабильный контракт. Минус — они глобальные и «все на них равняются»: разные пакеты могут вернуть один sentinel по разным поводам, и проверка станет слишком широкой. Эволюция подхода в Go: от sentinel к типизированным ошибкам (свой тип с полями — сравнение через errors.As) и к обёрткам с контекстом. В ответе стоит сказать: и то и то законно, выбор — между «одинаковая причина» (sentinel) и «нужны детали» (тип).

## Что делает defer и когда в нём нужен recover?

recover имеет смысл ТОЛЬКО внутри отложенной функции: defer func() { if r := recover(); r != nil { … } }(). Он останавливает раскрутку паники и возвращает её значение — так пишут обёртки над чужим кодом (HTTP-хендлеры, воркеры очередей), чтобы паника в одной задаче не убивала процесс. recover вне defer бесполезен (вернёт nil). Важно проговорить границу: recover — для восстановления на границе модуля (обработать, залогировать, вернуть 500), а не для управления обычным потоком; ошибки — значениями, паника — аварией.

```go
func safeWorker(job Job) {
    defer func() {
        if r := recover(); r != nil {
            log.Printf("воркер упал: %v\n%s", r, debug.Stack())
        }
    }()
    run(job) // паника здесь не убьёт процесс
}
```

## В каком порядке выполняются defer и return? Вопрос про именованный возврат.

return в Go — два шага: (1) присвоить возвращаемым переменным значения, (2) выполнить отложенные вызовы, (3) выйти. С именованными возвратами (func f() (err error)) defer может изменить результат: в отложенной функции err = fmt.Errorf(...) перезапишет то, что вернул return. Это база паттерна «defer меняет err»: повторно закрыть ресурс, добавить контекст через errors.Join. Ловушка: голый return с изменённым именованным err — читаемость падает, о такой модификации надо заявлять комментарием.

```go
func read() (err error) {
    defer func() {
        if err != nil {
            err = fmt.Errorf("read: %w", err) // добавили контекст
        }
    }()
    _, err = os.ReadFile("x")
    return
}
```

## Паника в горутине — что произойдёт? Как защитить сервис?

Паника, не восстановленная в горутине, убивает ВЕСЬ процесс — не только эту горутину. Стек-трейс печатается, программа падает. Для сервисов с горутинами-воркерами значит: каждая «чужая» точка входа (задача из очереди, хендлер, колбэк) получает defer-recover обёртку, которая логирует панику со стеком и продолжает работу. Стандартная HTTP-библиотека так делает для хендлеров, а для своих воркеров обёртку пишут сами или берут errgroup/пул с recover. Ответ стоит закончить: recover — страховка на границе, а не замена обработке ошибок внутри.

## Когда паниковать самому?

Короткий список. Невозможное состояние, которое означает ошибку программиста: переключатель дошёл до невалидной ветки, инвариант структуры нарушен после проверки. Инициализация: обязательный конфиг не задан — паника в init/конструкторе честнее, чем работать с молча неверными данными. В публичных API библиотек — почти никогда: возвращайте ошибки, решение за вызывающим. Запрещённые кейсы: валидация пользовательского ввода (это ошибка, не паника), ретраебельные сбои сети/базы.
