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

Как перехватить панику при использовании Defer?

1.3 Junior🔥 71 комментариев
#Основы Go

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

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

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

Перехват паники с использованием defer и recover

В Go паника (panic) — это аварийное состояние, которое приводит к остановке выполнения текущей горутины. Для корректной обработки таких ситуаций используется связка defer и встроенной функции recover. defer гарантирует выполнение отложенных вызовов даже при возникновении паники, а recover позволяет перехватить панику и восстановить нормальное выполнение программы.

Основной механизм перехвата

Ключевой принцип: функция recover работает только внутри отложенной функции (defer). При вызове вне defer она вернёт nil. При возникновении паники recover возвращает значение, переданное в panic().

Базовый пример

func safeOperation() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Printf("Перехвачена паника: %v\n", r)
            // Здесь можно выполнить логирование, очистку ресурсов и т.д.
        }
    }()
    
    // Код, который может вызвать панику
    panic("критическая ошибка!")
    
    fmt.Println("Эта строка не будет выполнена")
}

func main() {
    safeOperation()
    fmt.Println("Программа продолжает работу после паники")
}

Детали реализации

  1. Порядок выполнения defer при панике:
    При возникновении паники Go начинает раскручивать стек (unwind), выполняя все отложенные функции (defer) в обратном порядке (LIFO). Если в одной из них вызывается recover, паника останавливается.

  2. Возвращаемое значение recover:

    • Если паники не было — recover возвращает nil
    • Если паника была — возвращает значение, переданное в panic()
    • Тип возвращаемого значения — any (interface{})
func handlePanic() {
    defer func() {
        if r := recover(); r != nil {
            switch v := r.(type) {
            case string:
                fmt.Printf("Строковая паника: %s\n", v)
            case error:
                fmt.Printf("Ошибка: %v\n", v)
            default:
                fmt.Printf("Неизвестная паника: %v\n", v)
            }
        }
    }()
    
    // Можно вызывать panic с разными типами
    panic(errors.New("ошибка времени выполнения"))
}

Важные аспекты и лучшие практики

1. Область действия recover

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

func main() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Перехват в main:", r)
        }
    }()
    
    go func() {
        panic("паника в горутине") // Эта паника НЕ будет перехвачена в main
    }()
    
    time.Sleep(time.Second)
    fmt.Println("Main завершается")
}

2. Идиоматические паттерны

Паттерн с возвращением ошибки
func safeDivide(a, b int) (result int, err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("паника при делении: %v", r)
        }
    }()
    
    if b == 0 {
        panic("деление на ноль")
    }
    
    return a / b, nil
}
Паттерн с повторной паникой

Иногда нужно перехватить панику, выполнить очистку, но затем сгенерировать её снова:

func processWithCleanup() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Выполняем очистку ресурсов...")
            // Повторная паника с оригинальным значением
            panic(r)
        }
    }()
    
    // Код, который может паниковать
}

3. Ограничения и предостережения

  • Не использовать recover для обычного контроля потока — это антипаттерн. Вместо panic/recover для обработки ожидаемых ошибок используйте возврат ошибок (error).
  • recover не перехватывает фатальные ошибки, такие как выход за пределы памяти (out of memory) или deadlock.
  • После успешного recover программа продолжает выполнение следующей инструкции после отложенной функции, а не с места паники.

Практический пример: веб-сервер

func handleRequest(w http.ResponseWriter, r *http.Request) {
    defer func() {
        if r := recover(); r != nil {
            log.Printf("Паника при обработке запроса: %v", r)
            http.Error(w, "Внутренняя ошибка сервера", http.StatusInternalServerError)
        }
    }()
    
    // Опасная операция
    processRequest(r)
    
    w.Write([]byte("Успешный ответ"))
}

func processRequest(r *http.Request) {
    // Имитация условия для паники
    if r.URL.Path == "/panic" {
        panic("искусственная паника")
    }
}

Заключение

Связка defer + recover — это мощный механизм для обработки непредвиденных аварийных ситуаций в Go. Ключевые правила:

  • Размещайте recover только внутри отложенных функций
  • Используйте этот механизм для грациозной обработки действительно исключительных ситуаций
  • Всегда выполняйте необходимую очистку ресурсов в defer
  • Помните, что recover действует только в текущей горутине

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

Как перехватить панику при использовании Defer? | PrepBro