Ошибки и defer

defer: порядок выполнения и отличие ожидаемой ошибки от panic

Содержание курса

panic vs error: аварийная раскрутка стека против ожидаемого сбоя

error — это значение. Функция его возвращает, вызывающий код получает, смотрит на него и решает, что делать: повторить запрос, залогировать, вернуть выше. Программа продолжает работать в штатном режиме.

panic работает принципиально иначе. Когда Go встречает panic("что-то сломалось"), обычное выполнение текущей функции прекращается немедленно. Дальше происходит раскрутка стека вызовов: Go поднимается по цепочке вызвавших функций, и в каждой из них выполняет зарегистрированные defer. После того как стек раскручен до верхнего уровня, программа завершается аварийно и печатает сообщение о панике и трассировку.

Вот минимальный пример:

package main

import "fmt"

func riskyOp() {
    panic("что-то сломалось")
}

func main() {
    defer fmt.Println("defer в main сработал")
    riskyOp()
}

Вывод будет выглядеть примерно так (конкретные пути и номера строк зависят от окружения):

defer в main сработал
panic: что-то сломалось

goroutine 1 [running]:
main.riskyOp(...)
    .../main.go:...
main.main()
    .../main.go:...
exit status 2

Посмотри на порядок: сначала при раскрутке стека выполняется defer fmt.Println, поэтому появляется строка defer в main сработал. Затем, поскольку panic не была перехвачена, программа печатает panic: что-то сломалось и трассировку и завершается. Это подтверждает, что defer срабатывает и во время раскрутки стека из-за panic, а не только при обычном return.

Почему нельзя использовать panic вместо error для обычных ошибок?

Во-первых, вызывающий код теряет контроль. Если функция вернула error, вызывающий может его проверить, обработать или передать выше. Если функция сделала panic, вызывающий код не может перехватить это обычным if err != nil — программа уже летит к завершению.

Во-вторых, это нарушает соглашение Go. Невалидный ввод, недоступная запись, несовпадение формата — всё это ожидаемые ситуации, для которых язык предоставляет error. panic предназначен для ситуаций, которые означают ошибку самого программиста или нарушение инварианта, который «никогда не должен произойти» при правильной логике программы.

Практически: если ты пишешь функцию divide(a, b int) и b == 0 — возвращай error. Если где-то внутри твоей структуры данных обнаруживается состояние, которое физически не может возникнуть при корректной работе кода, — panic уместен как сигнал о баге в самой программе, а не о внешней ситуации.

Практика defer и обработки ошибок в Go. Попробуйте задачу на порядок отложенных вызовов и ранний return, затем — на обёртку ошибки и errors.Is.

Начать заново?

Помощник начнёт новый диалог по этой задаче. Текущая переписка останется в истории до конца срока хранения.

О переписке

Можно приложить до трёх фото PNG, JPEG или WebP по 5 МБ.

Переписка и фото хранятся 90 дней после последнего принятого сообщения. Преподаватель получит доступ к диалогу, когда вы попросите его помочь.

Проверка решения остаётся отдельным действием.