Menu
Coddy logo textTech

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 для действительно исключительных ситуаций, таких как поврежденное состояние или неустранимые условия.

challenge icon

Задание

Легко

Давайте создадим безопасный калькулятор деления, который демонстрирует, как использовать 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(&dividend)
	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]"
	// в зависимости от того, была ли возвращена ошибка
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование