Вызов recover вынесен из deferred-функции во вспомогательный метод: почему паника не будет перехвачена?
recover перехватывает панику только при прямом вызове из функции, зарегистрированной через defer, во время активной раскрутки стека. Если deferred-функция вызывает обычный вспомогательный метод, а уже тот вызывает recover, результатом будет nil, и паника продолжит раскрутку.
Модель panic/defer/recover в Go предназначена для отделения аварийного выхода из глубоко вложенного кода от обычной обработки ошибок через возвращаемое значение. defer обеспечивает выполнение завершающей логики при выходе из функции, а recover позволяет определить контролируемую границу, на которой паника преобразуется в обычный результат или ошибку.
Ограничение на прямой вызов делает границу восстановления явной. Иначе любой вспомогательный вызов внутри deferred-логики мог бы неявно перехватывать панику и скрывать место, где принято решение продолжить работу.
Разработчик выносит общую логику восстановления в helper и вызывает его из deferred-функции. Внешне кажется, что helper выполняется во время паники, поэтому его recover должен сработать.
На практике recover в helper возвращает nil. Паника не считается обработанной, продолжает раскрутку стека и может завершить горутину с аварийным сообщением. Если после этого код рассчитывает на преобразование паники в ошибку, middleware или серверный обработчик не выполнит свою задачу.
Специальное состояние активной паники доступно для восстановления только непосредственному телу deferred-функции. Вызов должен быть синтаксически и семантически прямым: нельзя сначала передать его через helper, замыкание или другую функцию.
В этом примере helper напечатает nil, после чего паника продолжит раскрутку. Исправление состоит в прямом вызове recover внутри deferred-функции, а уже полученное значение можно передать helper для дальнейшей обработки.
После успешного recover deferred-функция должна классифицировать значение паники, записать диагностическую информацию и вернуть управление через завершение функции. Сам факт вызова recover не делает произвольные данные безопасными: значение может быть любого типа, включая nil, поэтому преобразование в error требует отдельной проверки.
Ограничение действует в пределах той же горутины: recover не предназначен для перехвата паники другой горутины. На практике восстановление обычно размещают на четко определенной границе — например, в обработчике запроса, где панику можно преобразовать в ответ об ошибке и сохранить контекст для логирования.
HTTP-сервис хочет единообразно перехватывать паники из обработчиков. Команда выносит вызов recover в общий helper, чтобы middleware выглядел короче. Такой вариант формально переиспользуем, но небезопасен: helper не перехватит панику, и процесс обработки запроса завершится аварийно.
Второй вариант — разместить прямой вызов recover в deferred-функции middleware, а полученное значение передать в helper для классификации и логирования. Он немного менее компактен, зато корректно соблюдает семантику Go и сохраняет единый формат обработки.
Выбран второй вариант: middleware напрямую вызывает recover, проверяет тип значения и формирует ответ. В результате паники на границе запроса не утекают наружу, а обычные ошибки по-прежнему проходят через стандартный путь возврата error.
recover, если deferred-функция сначала вызовет другую функцию, а затем напрямую вызовет recover?Да. Важно, что сам вызов recover в этот момент выполняется непосредственно из тела deferred-функции. Предыдущий вызов другой функции не меняет состояние паники и не запрещает последующий прямой recover.
recover как значение функции в deferred-функцию?Нет, это нарушает требование прямого вызова. Если deferred-функция вызовет сохраненное значение функции, фактический вызов произойдет через дополнительный уровень, поэтому recover вернет nil, а паника продолжится.
recover?Раскрутка паники прекращается, и функция, находившаяся в процессе аварийного завершения, возвращает управление обычным образом после выполнения своих отложенных вызовов. Выполнение не продолжается с места panic: инструкции после него не запускаются. Управление получает вызывающий код уже после возврата этой функции.