Menu
Coddy logo textTech

Panic, Defer и Recover

Часть раздела Объектно-ориентированное программирование путешествия по GO на Coddy. Урок 57 из 107.

Хотя Go делает акцент на возврате errors, некоторые ситуации действительно невозможно исправить. Для таких случаев Go предоставляет panic, defer и recover — механизм обработки исключительных обстоятельств.

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

func MustGetConfig(key string) string {
    value, exists := config[key]
    if !exists {
        panic("missing required config: " + key)
    }
    return value
}

defer планирует выполнение функции при возврате окружающей функции — независимо от того, происходит ли это обычным образом или из-за panic. Отложенные вызовы execute в обратном порядке (последним добавлен — первым выполнен):

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
}

Этот шаблон converts panic в обычный error, позволяя вашей программе продолжить работу. Используйте его sparingly: предпочитайте возвращать errors для ожидаемых случаев сбоя, а panic/recover оставляйте для действительно исключительных ситуаций, таких как повреждённое состояние или неисправимые условия.

challenge icon

Задание

Легко

Давайте создадим безопасный калькулятор деления, который демонстрирует, как использовать panic, defer и recover, чтобы корректно обрабатывать исключительные ситуации. Калькулятор будет выполнять потенциально опасные операции и преобразовывать panic в обычные error, которые можно обрабатывать стандартным образом.

Вы организуете код в двух файлах:

  • calculator.go: создайте логику деления с восстановлением после panic.

    Реализуйте Divide function, которая принимает два целых числа и вызывает panic с сообщением "division by zero", если divisor равен нулю. В противном случае она возвращает целочисленный результат деления.

    Реализуйте SafeDivide function, которая принимает два целых числа и возвращает целочисленный результат и error. Эта function должна:

    • Использовать defer с anonymous function для восстановления после любого panic
    • Если panic был recovered, преобразовать его в error с форматом "calculation error: [panic message]"
    • Внутри вызвать function Divide
    • Вернуть результат и nil при успешном выполнении или ноль и error, если panic был recovered
  • main.go: считайте два целых числа из input и используйте SafeDivide, чтобы безопасно выполнить calculation. Также продемонстрируйте defer, выводя сообщения об очистке в правильном order.

    Ваша main function должна:

    • Вывести Starting calculation в начале
    • Использовать defer, чтобы запланировать вывод Cleanup complete
    • Использовать ещё один defer, чтобы запланировать вывод Releasing resources
    • Вызвать SafeDivide с input values
    • Вывести либо Result: [value], либо Error: [error message] в зависимости от результата

Будут предоставлены следующие input:

  • Строка 1: First целое число (dividend)
  • Строка 2: Второе целое число (divisor)

Помните, что deferred calls выполняются в reverse order (last-in, first-out), поэтому сообщения об очистке должны появиться в порядке, противоположном порядку их deferred.

Например, для 20 и 4 ваш Output должен быть таким:

Starting calculation
Result: 5
Releasing resources
Cleanup complete

А для 10 и 0 ваш Output должен быть таким:

Starting calculation
Error: calculation error: division by zero
Releasing resources
Cleanup complete
REQUIRED OUTPUT FORMAT: [Your translated content here]

Попробуйте сами

package main

import (
	"fmt"
)

func main() {
	// Прочитать ввод
	var dividend, divisor int
	fmt.Scanln(&dividend)
	fmt.Scanln(&divisor)

	// Вывести начальное сообщение
	fmt.Println("Starting calculation")

	// TODO: Use defer to schedule "Cleanup complete" message

	// TODO: Use defer to schedule "Releasing resources" message
	// Помните: отложенные вызовы выполняются в порядке LIFO (last-in, first-out)

	// TODO: Вызвать SafeDivide с входными значениями

	// TODO: Вывести либо "Result: [value]", либо "Error: [error message]"
	// в зависимости от того, была ли возвращена ошибка
}
quiz iconПроверьте себя

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

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

Потренируйтесь самостоятельно: Онлайн-компилятор Go