Везде ли можно поймать панику с помощью Recover
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Краткий ответ
Нет, recover() можно использовать только внутри отложенных функций (defer) и при соблюдении определенных условий. Panic в Go — механизм аварийной остановки выполнения, и его обработка не такая гибкая, как исключений в других языках.
Где recover() работает, а где нет
✅ Где recover() сработает
Recover работает только при выполнении следующих условий:
- Вызов исключительно внутри отложенной функции (defer)
- В той же goroutine, где произошла паника
- Когда паника "разворачивает стек" именно через этот recover
func safeFunction() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Паника перехвачена:", r)
}
}()
panic("критическая ошибка") // recover() поймает эту панику
}
❌ Где recover() НЕ сработает
- Прямой вызов вне defer
func wrongExample() {
// Это НЕ сработает
if r := recover(); r != nil {
fmt.Println("Этот код никогда не выполнится")
}
panic("ошибка")
}
- В другой goroutine
func differentGoroutine() {
go func() {
defer func() {
if r := recover(); r != nil {
// Это поймает панику ТОЛЬКО в этой goroutine
fmt.Println("Паника в дочерней goroutine:", r)
}
}()
panic("ошибка в goroutine")
}()
// recover() здесь НЕ поймает панику из дочерней goroutine
}
- После вызова os.Exit() или фатальной ошибки
func fatalExample() {
defer func() {
// Это НЕ сработает
recover()
}()
os.Exit(1) // Немедленное завершение, паники нет
}
- Когда паника происходит в коде C через cgo
// При панике в нативном C-коде recover() обычно бессилен
Практические примеры обработки
✅ Корректный паттерн использования
func processData(data []int) (result int, err error) {
defer func() {
if r := recover(); r != nil {
// Преобразуем панику в обычную ошибку
err = fmt.Errorf("паника при обработке: %v", r)
}
}()
// Код, который может запаниковать
result = data[len(data)] // Возможная паника: индекс вне диапазона
return result, nil
}
❌ Распространенная ошибка
func incorrectRecover() {
// recover() здесь НЕ сработает, потому что не в defer
panic("ошибка")
defer func() {
recover() // Слишком поздно - до этой строки выполнение не дойдет
}()
}
Вложенные defer и recover
Важно понимать порядок выполнения при вложенных defer:
func nestedDeferExample() {
defer func() {
fmt.Println("Defer 1 - начало")
if r := recover(); r != nil {
fmt.Println("Defer 1 поймал:", r)
}
fmt.Println("Defer 1 - конец")
}()
defer func() {
fmt.Println("Defer 2 - начало")
panic("паника в defer 2")
fmt.Println("Defer 2 - конец")
}()
panic("первоначальная паника")
}
// Output:
// Defer 2 - начало
// Defer 1 поймал: паника в defer 2
// Defer 1 - конец
Рекомендации по использованию recover()
✅ Правильные подходы:
- Обработка паник на верхнем уровне обработчиков HTTP, горутин
- Изоляция ненадежного кода (парсинг, работа с внешними библиотеками)
- Грациозное завершение с логированием и очисткой ресурсов
❌ Избегайте:
- Использования recover() для контроля потока (это не исключения!)
- Подавления паник без логирования
- Восстановления после паник в критических секциях
Пример грамотного применения в веб-сервере
func safeHandler(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if r := recover(); r != nil {
log.Printf("Паника в обработчике: %v\n%s",
r, debug.Stack())
http.Error(w, "Внутренняя ошибка сервера",
http.StatusInternalServerError)
}
}()
next.ServeHTTP(w, r)
})
}
Вывод
Recover() — не панацея, а инструмент для специфических случаев. Он работает только в четко определенных границах языка Go:
- Только внутри defer
- Только для текущей goroutine
- Только когда паника активно разворачивает стек
Используйте recover() осмотрительно, в основном для предотвращения аварийного завершения на верхних уровнях приложения, и помните, что паника — это для исключительных ситуаций, а не для обычного контроля потока выполнения. Правильная обработка ошибок через возвращаемые значения error почти всегда предпочтительнее использования panic/recover.