← Назад к вопросам

Везде ли можно поймать панику с помощью Recover

2.0 Middle🔥 141 комментариев
#Основы Go

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Краткий ответ

Нет, recover() можно использовать только внутри отложенных функций (defer) и при соблюдении определенных условий. Panic в Go — механизм аварийной остановки выполнения, и его обработка не такая гибкая, как исключений в других языках.

Где recover() работает, а где нет

Где recover() сработает

Recover работает только при выполнении следующих условий:

  1. Вызов исключительно внутри отложенной функции (defer)
  2. В той же goroutine, где произошла паника
  3. Когда паника "разворачивает стек" именно через этот recover
func safeFunction() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Паника перехвачена:", r)
        }
    }()
    
    panic("критическая ошибка") // recover() поймает эту панику
}

Где recover() НЕ сработает

  1. Прямой вызов вне defer
func wrongExample() {
    // Это НЕ сработает
    if r := recover(); r != nil { 
        fmt.Println("Этот код никогда не выполнится")
    }
    panic("ошибка")
}
  1. В другой goroutine
func differentGoroutine() {
    go func() {
        defer func() {
            if r := recover(); r != nil {
                // Это поймает панику ТОЛЬКО в этой goroutine
                fmt.Println("Паника в дочерней goroutine:", r)
            }
        }()
        panic("ошибка в goroutine")
    }()
    
    // recover() здесь НЕ поймает панику из дочерней goroutine
}
  1. После вызова os.Exit() или фатальной ошибки
func fatalExample() {
    defer func() {
        // Это НЕ сработает
        recover()
    }()
    
    os.Exit(1) // Немедленное завершение, паники нет
}
  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.

Везде ли можно поймать панику с помощью Recover | PrepBro