Go Academy · Урок

panic, recover и defer

Когда вызывать panic и как корректно восстанавливаться

Урок 4 из 412 шагов

«panic, recover и defer» — бесплатный урок Go Academy на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Go Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Go Academy содержит 4 уроков всего.

Что такое panic?

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

package main
import "fmt"

func mustPositive(n int) int {
    if n <= 0 {
        panic(fmt.Sprintf("mustPositive: got %d", n))
    }
    return n
}

func main() {
    fmt.Println(mustPositive(5))  // 5
    // fmt.Println(mustPositive(-1)) // panics!
}

Когда использовать panic

Используйте panic только в следующих случаях:

  • Действительно неустранимые ошибки программиста (нарушенные инварианты)
  • Невозможные условия, указывающие на ошибку в программе
  • Сбои при запуске (отсутствующая конфигурация, неудачные подключения во время инициализации)

Никогда не используйте panic для ожидаемых условий отказа — вместо этого используйте error.

Основы defer

defer планирует вызов функции на момент возврата окружающей функции, независимо от способа возврата (обычный возврат, ошибка или паника):

package main
import "fmt"

func main() {
    defer fmt.Println("third")
    defer fmt.Println("second")  // LIFO order
    defer fmt.Println("first")
    fmt.Println("running...")
    // Output:
    // running...
    // first
    // second
    // third
}

defer для очистки

Наиболее распространённое применение defer — обеспечение освобождения ресурсов:

package main
import ("fmt"; "os")

func readFile(path string) (string, error) {
    f, err := os.Open(path)
    if err != nil {
        return "", fmt.Errorf("open: %w", err)
    }
    defer f.Close()  // always runs, even if we return early

    buf := make([]byte, 1024)
    n, _ := f.Read(buf)
    return string(buf[:n]), nil
}

recover — перехват паники

recover останавливает панику и возвращает её значение. Его необходимо вызывать непосредственно внутри функции defer:

package main
import "fmt"

func safeDiv(a, b int) (result int, err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("recovered from panic: %v", r)
        }
    }()
    return a / b, nil  // panics if b==0
}

func main() {
    r, err := safeDiv(10, 0)
    fmt.Println(r, err)
    // 0 recovered from panic: runtime error: integer divide by zero
}

recover должен вызываться в отложенной функции

recover работает только при непосредственном вызове внутри отложенной функции. При вызове в другом месте он возвращает nil:

package main
import "fmt"

func wrong() {
    // recover() here does nothing — not in defer
}

func correct() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("caught:", r)
        }
    }()
    panic("oops")
}

func main() { correct() }

panic в коде библиотеки

Код библиотеки почти никогда не должен позволять паникам доходить до вызывающего кода. Преобразуйте паники в ошибки на границе:

package main
import ("fmt"; "runtime/debug")

func safeCall(fn func()) (err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("panic: %v\n%s", r, debug.Stack())
        }
    }()
    fn()
    return nil
}

func main() {
    err := safeCall(func() { panic("boom") })
    fmt.Println(err != nil) // true
}

Порядок defer по принципу LIFO и ловушка с циклами

Отложенные вызовы выполняются в порядке «последним добавлен — первым выполнен» (LIFO). Будьте осторожны, откладывая вызовы внутри циклов — используйте замыкание или вспомогательную функцию:

package main
import "fmt"

func processFiles(names []string) {
    for _, name := range names {
        name := name  // capture loop variable
        defer func() { fmt.Println("close:", name) }()
    }
}

func main() {
    processFiles([]string{"a.txt", "b.txt", "c.txt"})
    // Output (LIFO): close: c.txt, close: b.txt, close: a.txt
}

defer и именованные возвращаемые значения

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

package main
import "fmt"

func doWork() (err error) {
    defer func() {
        if err != nil {
            err = fmt.Errorf("doWork: %w", err)
        }
    }()
    return fmt.Errorf("something failed")
}

func main() {
    err := doWork()
    fmt.Println(err) // doWork: something failed
}

panic и ошибка: руководство по выбору

Выбор между panic и ошибкой:

  • Используйте ошибку: ожидаемые сбои (файл не найден, превышение времени ожидания сети, недопустимый ввод)
  • Используйте panic: ошибки программирования (доступ к nil-карте, выход за границы), невозможные состояния, сбои инициализации
  • Никогда не используйте panic через границы API — вызывающий код не сможет его обработать
  • Перехватывайте паники на верхнем уровне горутины в серверах, чтобы избежать сбоев

Быстрая проверка

Где необходимо вызывать recover(), чтобы перехватить панику?

Повторение: panic, recover, defer

Итоги:

  • panic: раскручивает стек, выполняет отложенные вызовы и приводит к аварийному завершению, если не был перехвачен
  • defer: выполняется при выходе из функции (обычном, из-за ошибки или паники); в порядке LIFO
  • recover: останавливает панику и возвращает значение; должен вызываться в defer
  • Используйте панику для ошибок программиста, а не для ожидаемых сбоев
  • Преобразуйте паники в ошибки на границах API
Можно начать бесплатно

Изучай Go с ИИ-репетитором — бесплатно

Пиши и запускай код прямо в браузере, получай мгновенную помощь от ИИ-репетитора 24/7 и продолжи учиться на сайте или в приложении.

Курсы
51
Уроки
203

Часто задаваемые вопросы

Урок «panic, recover и defer» бесплатный?

Да — полный текст урока «panic, recover и defer» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Go Academy, подпишись на CoddyKit PRO. Курс Go Academy содержит 4 уроков всего.

Чему я научусь в уроке «panic, recover и defer»?

Когда вызывать panic и как корректно восстанавливаться Ты практикуешь Go Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать Go Academy?

Предыдущий опыт не требуется. Go Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.

Сколько времени занимает урок «panic, recover и defer»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке Go Academy?

Да. Каждый урок Go Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Интерфейс error
  2. Создание пользовательских ошибок
  3. Обёртывание и распаковка ошибок
  4. panic, recover и defer
← Назад к Go Academy