← Назад к вопросам
Recover после panic
2.0 Middle🔥 121 комментариев
#Основы Go
Условие
Напишите функцию, которая выполняет переданную функцию и перехватывает panic, возвращая ошибку вместо падения программы.
Сигнатура
func safeExecute(fn func()) (err error)
Требования
- Если fn выполнилась без panic - вернуть nil
- Если fn вызвала panic - перехватить её и вернуть ошибку с сообщением из panic
- Программа не должна падать
Пример
err := safeExecute(func() {
panic("something went wrong")
})
// err.Error() == "recovered from panic: something went wrong"
err = safeExecute(func() {
fmt.Println("all good")
})
// err == nil
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Решение: Recover после panic
Основная идея
Используем defer с recover() для перехвата паники:
- defer выполняется при выходе из функции, включая panic
- recover() возвращает значение, переданное panic
- Если паники не было, recover() вернёт nil
Простая реализация
import (
"fmt"
)
func safeExecute(fn func()) (err error) {
defer func() {
if r := recover(); r != nil {
// Преобразуем паику в ошибку
switch v := r.(type) {
case string:
err = fmt.Errorf("recovered from panic: %s", v)
case error:
err = fmt.Errorf("recovered from panic: %w", v)
default:
err = fmt.Errorf("recovered from panic: %v", v)
}
}
}()
fn() // Выполняем функцию, может паниковать
return nil
}
Как работает:
- defer регистрирует функцию восстановления
- fn() выполняется
- Если panic → defer выполняется автоматически
- recover() ловит паику и возвращает значение
- Преобразуем паику в error и возвращаем
Пример использования
func main() {
// Тест 1: с паникой
err := safeExecute(func() {
panic("something went wrong")
})
fmt.Println(err) // recovered from panic: something went wrong
// Тест 2: без паники
err = safeExecute(func() {
fmt.Println("all good")
})
fmt.Println(err) // <nil>
// Тест 3: паника с error
err = safeExecute(func() {
panic(fmt.Errorf("custom error"))
})
fmt.Println(err) // recovered from panic: custom error
}
Версия с типом паники
func safeExecute(fn func()) (err error) {
defer func() {
if r := recover(); r != nil {
switch v := r.(type) {
case string:
// Паника был строкой
err = fmt.Errorf("panic: %s", v)
case error:
// Паника был error интерфейсом
err = fmt.Errorf("panic: %w", v)
case int:
// Паника было число
err = fmt.Errorf("panic: %d", v)
default:
// Неизвестный тип
err = fmt.Errorf("panic: %v (type: %T)", v, v)
}
}
}()
fn()
return nil
}
Версия с logrus (для production)
import (
"fmt"
"github.com/sirupsen/logrus"
)
func safeExecute(fn func()) (err error) {
defer func() {
if r := recover(); r != nil {
logrus.WithField("panic", r).Error("Recovered from panic")
switch v := r.(type) {
case string:
err = fmt.Errorf("recovered from panic: %s", v)
case error:
err = fmt.Errorf("recovered from panic: %w", v)
default:
err = fmt.Errorf("recovered from panic: %v", v)
}
}
}()
fn()
return nil
}
Версия с возвращаемым результатом и ошибкой
func safeExecuteWithResult(fn func() (interface{}, error)) (result interface{}, err error) {
defer func() {
if r := recover(); r != nil {
result = nil
switch v := r.(type) {
case string:
err = fmt.Errorf("recovered from panic: %s", v)
case error:
err = fmt.Errorf("recovered from panic: %w", v)
default:
err = fmt.Errorf("recovered from panic: %v", v)
}
}
}()
return fn()
}
Версия для методов с receiver
type Handler struct {}
func (h *Handler) safeExecute(fn func()) (err error) {
defer func() {
if r := recover(); r != nil {
// Логируем для отладки
fmt.Printf("[%s] Recovered from panic: %v\n", h.String(), r)
switch v := r.(type) {
case string:
err = fmt.Errorf("recovered from panic: %s", v)
case error:
err = fmt.Errorf("recovered from panic: %w", v)
default:
err = fmt.Errorf("recovered from panic: %v", v)
}
}
}()
fn()
return nil
}
Версия с timeout
import (
"context"
"fmt"
"time"
)
func safeExecuteWithContext(ctx context.Context, fn func(context.Context)) (err error) {
defer func() {
if r := recover(); r != nil {
switch v := r.(type) {
case string:
err = fmt.Errorf("recovered from panic: %s", v)
case error:
err = fmt.Errorf("recovered from panic: %w", v)
default:
err = fmt.Errorf("recovered from panic: %v", v)
}
}
}()
fn(ctx)
return nil
}
Тестирование
import "testing"
func TestSafeExecute(t *testing.T) {
tests := []struct {
name string
fn func()
shouldErr bool
errMsg string
}{
{
name: "no panic",
fn: func() { fmt.Println("ok") },
shouldErr: false,
},
{
name: "string panic",
fn: func() { panic("error!") },
shouldErr: true,
errMsg: "recovered from panic: error!",
},
{
name: "error panic",
fn: func() { panic(fmt.Errorf("custom")) },
shouldErr: true,
errMsg: "recovered from panic: custom",
},
{
name: "int panic",
fn: func() { panic(42) },
shouldErr: true,
errMsg: "recovered from panic: 42",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := safeExecute(tt.fn)
if tt.shouldErr && err == nil {
t.Errorf("Expected error, got nil")
}
if !tt.shouldErr && err != nil {
t.Errorf("Expected nil, got %v", err)
}
if err != nil && err.Error() != tt.errMsg {
t.Errorf("Expected %q, got %q", tt.errMsg, err.Error())
}
})
}
}
Когда использовать safeExecute
- HTTP handlers: оборнуть обработчик для ловли паники
- Горутины: обработать панику в горутине
- Плагины: выполнить код плагина безопасно
- User callbacks: выполнить callback без риска краша
- Worker pools: каждый worker защищён от паники
Лучшие практики
-
Всегда логируй панику:
logrus.WithField("panic", r).WithField("stack", debug.Stack()).Error("Panic occurred") -
Различай типы паники:
switch v := r.(type) { case string: // User panic("message") case error: // User panic(error) default: // Неожиданное } -
Не подавляй панику в критических местах: Если паника означает критическую ошибку, может быть лучше дать ей распространяться.
-
Используй recover только в defer: recover() работает только в defer, иначе вернёт nil.
Это essential для production Go кода — защита от неожиданных паник.