Panic, Defer и Recover
Часть раздела Объектно-ориентированное программирование путешествия по GO на Coddy — урок 57 из 107.
Хотя Go делает упор на возврат ошибок, некоторые ситуации являются действительно неустранимыми. Для таких случаев Go предоставляет panic, defer и recover — механизм для обработки исключительных ситуаций.
panic немедленно прекращает нормальное выполнение. Оно зарезервировано для программных ошибок, таких как обращение к индексу за пределами диапазона, а не для ожидаемых ошибок, таких как некорректный ввод пользователя:
func MustGetConfig(key string) string {
value, exists := config[key]
if !exists {
panic("missing required config: " + key)
}
return value
}defer планирует выполнение функции на момент завершения внешней функции — будь то обычный возврат или паника. Отложенные вызовы выполняются в обратном порядке (LIFO, «последним пришёл — первым вышел»):
func ProcessFile() {
fmt.Println("Opening file")
defer fmt.Println("Closing file")
fmt.Println("Processing...")
// Вывод: Opening file, Processing..., Closing file
}recover перехватывает панику и возобновляет нормальное выполнение. Она работает только внутри отложенной (deferred) функции:
func SafeOperation() (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("recovered from panic: %v", r)
}
}()
riskyOperation()
return nil
}Этот паттерн преобразует panic в обычную ошибку, позволяя вашей программе продолжить работу. Используйте его экономно — отдавайте предпочтение возврату ошибок для ожидаемых случаев сбоя и резервируйте panic/recover для действительно исключительных ситуаций, таких как поврежденное состояние или неустранимые условия.
Задание
ЛегкоДавайте создадим безопасный калькулятор деления, который демонстрирует, как использовать panic, defer и recover для изящной обработки исключительных ситуаций. Ваш калькулятор будет выполнять рискованные операции и преобразовывать паники в обычные ошибки, которые можно обрабатывать в штатном режиме.
Вы организуете свой код в двух файлах:
calculator.go: Создайте логику деления с восстановлением после паники.Реализуйте функцию
Divide, которая принимает два целых числа и вызывает панику с сообщением"division by zero", если делитель равен нулю. В противном случае она возвращает целочисленный результат деления.Реализуйте функцию
SafeDivide, которая принимает два целых числа и возвращает как целочисленный результат, так и ошибку. Эта функция должна:- Использовать
deferс анонимной функцией для восстановления после любой паники - Если паника восстановлена, преобразовать ее в ошибку в формате
"calculation error: [panic message]" - Вызывать функцию
Divideвнутри себя - Возвращать результат и
nilв случае успеха, или ноль и ошибку, если паника была восстановлена
- Использовать
main.go: Считайте два целых числа из входных данных и используйтеSafeDivideдля безопасного выполнения вычисления. Также продемонстрируйте работуdefer, выводя сообщения об очистке в правильном порядке.Ваша основная функция должна:
- Вывести
Starting calculationв самом начале - Использовать
deferдля планирования выводаCleanup complete - Использовать еще один
deferдля планирования выводаReleasing resources - Вызвать
SafeDivideс входными значениями - Вывести либо
Result: [value], либоError: [error message]в зависимости от результата
- Вывести
Будут предоставлены следующие входные данные:
- Строка 1: Первое целое число (делимое)
- Строка 2: Второе целое число (делитель)
Помните, что отложенные вызовы (deferred calls) выполняются в обратном порядке (последним пришел — первым обслужен, LIFO), поэтому ваши сообщения об очистке должны появляться в порядке, обратном тому, как они были отложены.
Например, для входных данных 20 и 4 ваш вывод должен быть следующим:
Starting calculation
Result: 5
Releasing resources
Cleanup completeА для 10 и 0 вывод должен быть таким:
Starting calculation
Error: calculation error: division by zero
Releasing resources
Cleanup completeШпаргалка
Go предоставляет panic, defer, и recover для обработки исключительных ситуаций.
panic немедленно останавливает нормальное выполнение и зарезервирована для неисправимых программных ошибок:
func MustGetConfig(key string) string {
value, exists := config[key]
if !exists {
panic("missing required config: " + key)
}
return value
}defer планирует запуск функции в момент выхода из окружающей функции. Отложенные вызовы выполняются в обратном порядке (последним вошел — первым вышел):
func ProcessFile() {
fmt.Println("Opening file")
defer fmt.Println("Closing file")
fmt.Println("Processing...")
// Вывод: Opening file, Processing..., Closing file
}recover перехватывает панику и возвращает нормальное выполнение. Она работает только внутри отложенной функции:
func SafeOperation() (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("recovered from panic: %v", r)
}
}()
riskyOperation()
return nil
}Этот паттерн преобразует панику в обычную ошибку. Рекомендуется возвращать ошибки для ожидаемых сбоев и оставлять panic/recover для действительно исключительных ситуаций.
Попробуйте сами
package main
import (
"fmt"
)
func main() {
// Чтение входных данных
var dividend, divisor int
fmt.Scanln(÷nd)
fmt.Scanln(&divisor)
// Вывод начального сообщения
fmt.Println("Starting calculation")
// TODO: Используйте defer, чтобы запланировать вывод сообщения "Cleanup complete"
// TODO: Используйте defer, чтобы запланировать вывод сообщения "Releasing resources"
// Помните: отложенные вызовы выполняются в порядке LIFO (last-in, first-out)
// TODO: Вызовите SafeDivide с входными значениями
// TODO: Выведите либо "Result: [value]", либо "Error: [error message]"
// в зависимости от того, была ли возвращена ошибка
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП в Go
Внешние файлыРабочее пространство и модули GoПакеты и импортыЭкспортируемые и неэкспортируемые именаВведение в ООП в GoСтруктуры как классыОпределение методов структурПолучатели-указатели и получатели-значенияИнициализация структурФункции-конструкторыИтоги — Простой калькулятор4Интерфейсы
Введение в интерфейсыНеявная реализацияИнтерфейс как контрактПустой интерфейс (any)Утверждение типаПереключатель типовКомпозиция интерфейсовИнтерфейсы Stringer и ErrorПовторение: Калькулятор фигур7Инкапсуляция
Экспортируемые и неэкспортируемые поляИнкапсуляция на уровне пакетовГеттеры и сеттерыСокрытие информации в GoИтоги — Записи о студентах10Обобщения (Generics) (Go 1.18+)
Введение в GenericsПараметры типовОграничения типовОбобщенные структурыОбходной путь для обобщенных методовИтоги — Обобщенная коллекция13Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн Abstract FactoryПаттерн ObserverПаттерн Strategy2Глубокое погружение в типы и структуры
Базовые и составные типыОпределение пользовательских типовТеги структурАнонимные структурыВложенные структурыНулевые значения и значения по умолчаниюПовторение — Контактная книга5Композиция вместо наследования
Почему в Go нет наследованияОсновы встраивания структурПродвижение методовВстраивание нескольких структурВстраивание против агрегацииЗатенение встроенных методовИтоги — Иерархия сотрудников8Обработка ошибок и ООП
Интерфейс errorПользовательские типы ошибокОбертывание ошибок (fmt.Errorf)Sentinel-ошибкиerrors.Is() и errors.As()Panic, Defer и RecoverИтоги — Парсер файлов11Стандартная библиотека и ООП
io.Reader и io.Writersort.InterfaceИнтерфейс fmt.Stringerencoding/json со структурамиИнтерфейс http.HandlerПовторение: модели REST API14Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикMiddleware как Декоратор3Указатели и память
Основы указателей в GoУказатели на структурыПередача по значению и по ссылкеФункция new()Сборка мусора в GoПовторение: Конструктор связного списка6Полиморфизм в Go
Полиморфизм через интерфейсыУтиная типизация в GoПравила реализации интерфейсовПолиморфные коллекцииВнедрение зависимостейИтоги — Обработчик платежей9Конкурентность и ООП
Основы горутинКаналы и взаимодействиеБуферизованные и небуферизованные каналыОператор selectsync.Mutex и sync.RWMutexsync.WaitGroupПроектирование потокобезопасных структурПовторение — Worker Pool