В мире программирования на Go есть два типа ошибок: обычные, с которыми можно вежливо договориться, и panic — внезапный крик программы, после которого она готова наложить на себя руки. Если вы видели сообщение panic: runtime error, значит, ваша программа столкнулась с чем-то настолько неожиданным, что предпочла немедленно прекратить существование, вместо того чтобы пытаться восстановиться. Давайте разберёмся, что стоит за этой драмой, как её предотвратить и стоит ли вообще бояться паники.
Что такое panic в Go?
Panic — это встроенный механизм языка Go для обработки критических ошибок времени выполнения (runtime errors). Это не обычная ошибка, возвращаемая через error. Это состояние, когда программа понимает, что не может продолжать работу в текущем контексте. Представьте, что вы пытаетесь прочитать книгу, а страницы внезапно исчезают — продолжать бессмысленно.
Важно: Panic не предназначен для обычной обработки ошибок в бизнес-логике. Это механизм для действительно исключительных ситуаций, когда продолжение работы может привести к повреждению данных или неопределённому поведению.
Типичные причины panic: классические сценарии катастрофы
Большинство panic возникают из-за операций, которые нарушают базовые гарантии безопасности языка.
1. Выход за границы слайса или массива
Самая частая причина. Попытка обратиться к элементу, которого не существует.
arr := []int{1, 2, 3}
fmt.Println(arr[10]) // panic: runtime error: index out of range [10] with length 3
2. Разыменование nil-указателя
Попытка использовать указатель, который ни на что не указывает.
var ptr *int
fmt.Println(*ptr) // panic: runtime error: invalid memory address or nil pointer dereference
3. Закрытие уже закрытого канала
Каналы в Go не любят, когда их закрывают дважды.
ch := make(chan int)
close(ch)
close(ch) // panic: close of closed channel
4. Вызов panic() вручную
Программист может сам инициировать панику с помощью встроенной функции panic().
panic("всё пропало, шеф!")
Recover: спасательный круг в бушующем море паники
Go предоставляет механизм recover — функцию, которая может перехватить panic, если её вызвать внутри отложенной функции (defer). Это единственный способ «поймать» панику и попытаться сохранить программу.
func safeFunction() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Паника перехвачена:", r)
}
}()
// Код, который может запаниковать
panic("тестовый взрыв")
}
Совет: Используйте recover только на границах вашего приложения — в main-функции, обработчиках HTTP-запросов или горутинах высокого уровня. Не маскируйте паники глубоко в коде, иначе вы скроете реальные проблемы.
Философия обработки ошибок в Go: panic vs error
Разработчики Go придерживаются чёткой философии:
- error — для ожидаемых проблем (файл не найден, сетевой сбой, неверный ввод пользователя)
- panic — для непредвиденных, катастрофических сбоев (нарушение инвариантов программы, невозможность выделить память)
Хорошая практика — проектировать код так, чтобы panic возникали редко, а большинство ситуаций обрабатывались через возврат ошибок.
Как отлаживать panic: практическое руководство
- Читайте стек вызовов: Go выводит подробный стектрейс, показывающий, где именно произошла паника.
- Используйте отладчик: Delve или встроенный отладчик вашей IDE помогут воспроизвести ситуацию.
- Добавляйте логирование: Записывайте состояние программы перед потенциально опасными операциями.
- Пишите тесты: Многие panic можно выявить с помощью табличных тестов, проверяющих граничные случаи.
FAQ: Часто задаваемые вопросы о panic в Go
В чём разница между panic и fatal error?
Panic можно перехватить с помощью recover, а вызов log.Fatal() или os.Exit() немедленно завершает программу без возможности восстановления.
Нужно ли всегда использовать recover?
Нет. В большинстве случаев panic указывает на серьёзную ошибку в логике программы. Лучше исправить причину, чем постоянно маскировать следствия.
Что происходит с горутинами при panic?
Panic убивает только ту горутину, в которой произошла. Другие горутины продолжают работу, если только паника не достигнет функции main.
Можно ли создать свою panic?
Да, с помощью встроенной функции panic(value). Но делайте это только в действительно исключительных ситуациях, когда продолжение работы невозможно.
Как избежать panic при работе с указателями?
Всегда проверяйте указатели на nil перед использованием и инициализируйте структуры перед работой с ними.