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

Какие знаешь способы отловить panic?

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

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

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

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

Способы отлова panic в Go

В Go panic представляет собой механизм для обработки исключительных ситуаций, когда программа не может продолжать нормальное выполнение. В отличие от ошибок, которые являются частью обычного потока управления, panic приводит к аварийной остановке программы, если их не перехватить. Вот основные способы отлова и обработки panic.

1. Встроенный механизм recover

Самый фундаментальный способ отлова panic — использование встроенной функции recover(). Она работает только внутри отложенных функций (deferred functions), и её задача — перехватить панику и восстановить нормальное выполнение программы.

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

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

Ключевые моменты:

  • recover() возвращает значение, переданное в panic()
  • Без defer функция recover() не будет работать
  • После успешного recover выполнение продолжается с точки после паники

2. Делегирование обработки через анонимные функции

Часто используют анонимные функции для создания обёрток с обработкой panic:

func withPanicHandling(fn func()) (err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("паника перехвачена: %v", r)
        }
    }()
    
    fn()
    return nil
}

func main() {
    if err := withPanicHandling(func() {
        panic("что-то пошло не так")
    }); err != nil {
        fmt.Println("Ошибка:", err)
    }
}

3. Глобальная обработка в goroutine

Для горутин необходимо обрабатывать panic внутри каждой из них, так как необработанная panic в горутине убьёт только эту горутину, но может привести к неожиданному поведению всей программы:

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

func main() {
    go safeGoroutine()
    time.Sleep(100 * time.Millisecond)
    fmt.Println("Основная программа работает")
}

4. Middleware-подход в веб-приложениях

В веб-фреймворках часто используют middleware для глобальной обработки panic:

func panicRecoveryMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        defer func() {
            if r := recover(); r != nil {
                log.Printf("HTTP паника: %v", r)
                http.Error(w, "Внутренняя ошибка сервера", http.StatusInternalServerError)
            }
        }()
        
        next.ServeHTTP(w, r)
    })
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/panic", func(w http.ResponseWriter, r *http.Request) {
        panic("что-то сломалось в обработчике")
    })
    
    wrappedMux := panicRecoveryMiddleware(mux)
    http.ListenAndServe(":8080", wrappedMux)
}

5. Кастомные обработчики через паттерны

Для сложных приложений можно реализовать кастомные обработчики:

type PanicHandler struct {
    RecoveryFunc func(interface{})
}

func (h *PanicHandler) Execute(fn func()) {
    defer func() {
        if r := recover(); r != nil {
            if h.RecoveryFunc != nil {
                h.RecoveryFunc(r)
            }
        }
    }()
    fn()
}

func main() {
    handler := &PanicHandler{
        RecoveryFunc: func(r interface{}) {
            fmt.Printf("Кастомная обработка: %v\n", r)
        },
    }
    
    handler.Execute(func() {
        panic("тестовая паника")
    })
}

Практические рекомендации

Что следует делать при перехвате panic:

  1. Логирование — всегда логируйте факт паники с контекстом
  2. Cleanup ресурсов — закрывайте файлы, соединения с БД
  3. Гранулярность — обрабатывайте panic на соответствующем уровне абстракции
  4. Преобразование в ошибки — часто panic преобразуют в обычные ошибки для вышележащих слоёв

Чего следует избегать:

  • Игнорирование panic — пустой recover без логирования
  • Слишком широкий catch — глобальный recover может скрывать реальные проблемы
  • Злоупотребление panic — не используйте panic для обычного управления потоком

Типичные сценарии использования:

// Сценарий 1: Защита критических секций
func UpdateDatabase(data interface{}) (err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("ошибка БД: %v", r)
            RollbackTransaction() // Cleanup
        }
    }()
    
    BeginTransaction()
    // Операции с БД, которые могут panic
    CommitTransaction()
    return nil
}

// Сценарий 2: Защита инициализации
func InitializeService(config Config) (service Service, err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("инициализация не удалась: %v", r)
        }
    }()
    
    service = NewService(config)
    return service, nil
}

Заключение

В Go panic следует использовать только для действительно исключительных ситуаций, а recover — для корректного завершения работы и cleanup операций. Правильная обработка panic повышает отказоустойчивость приложений, особенно в долгоживущих сервисах и серверных приложениях. Однако важно помнить, что Go поощряет явную обработку ошибок через возвращаемые значения, и panic/recover не должны заменять обычную обработку ошибок в бизнес-логике.