Когда вызывается panic
, в том числе неявно для ошибок времени выполнения (run-time errors), такие как индексация среза за пределами или сбой утверждения типа, оно немедленно останавливает выполнение текущей функции и начинает разматывать стек go-процедуры (goroutine), запуская любые отложенные функции по пути. Если это раскручивание достигает вершины стека go-процедуры, программа умирает. Тем не менее, можно использовать встроенную функцию restore
для восстановления контроля go-процедуры и возобновления нормального исполнения.
Вызов restore
останавливает раскручивание и возвращает аргумент переданный panic
. Поскольку единственный код, который работает во время разматывания - это код, находящийся внутри отложенных функций, restore
полезен только внутри отложенных функций.
Одно из применений restore
- отключение отказавшей go-процедуры внутри сервера, не убивая другие исполняемые go-процедуры.
func server(workChan <-chan *Work) {
for work := range workChan {
go safelyDo(work)
}
}
func safelyDo(work *Work) {
defer func() {
if err := recover(); err != nil {
log.Println("work failed:", err)
}
}()
do(work)
}
В этом примере, если do(work)
паникует, результат будет сохранен в логе и go-процедура будет выходить чисто, не нарушая другие. Нет необходимости делать что-либо еще в отсроченном закрытии; вызов restore
полностью обрабатывает ситуацию.
Поскольку restore
всегда возвращает nil
, если не вызывается напрямую из отложенной функции, отложенный код может вызывать библиотечные процедуры, которые сами по себе используют panic
и restore
без сбоев. В качестве примера, отложенная функция в safeDo
может вызывать функцию логирования перед вызовом restore
, и этот код логирования будет работать без изменений состояния паники.
С нашим шаблоном восстановления, do
функция (и все, что она вызывает) может выйти из любой плохой ситуации чисто, вызвав panic
. Мы можем использовать эту идею для упрощения обработку ошибок в сложном программном обеспечении. Давайте посмотрим на идеализированную версию пакета regexp
, который сообщает об ошибках парсиннга путем вызова panic
с локальным типом ошибки. Вот определение Error
, метода error
и функции Compile
.
// Error это тип ошибки разбора(парсинга);
// он удовлетворяет error интерфейсу
type Error string
func (e Error) Error() string {
return string(e)
}
// error это метод *Regexp,
// который сообщает об ошибках разбора (парсинга),
// вызывая panic с Error.
func (regexp *Regexp) error(err string) {
panic(Error(err))
}
// Compile возвращает разобранное представление
// регулярного выражения.
func Compile(str string) (regexp *Regexp, err error) {
regexp = new(Regexp)
// doParse будет вызывать panic
// если есть ошибка разбора.
defer func() {
if e := recover(); e != nil {
// Очищаем возвращаемое значение.
regexp = nil
// Будет повторная panic
// если нет ошибки разбора.
err = e.(Error)
}
}()
return regexp.doParse(str), nil
}
Если doParse
паникует, блок восстановления установит возвращаемое значение равным nil
- отложенные функции могут изменять именованные возвращаемые значения. Затем происходит проверка, в назначении err
, что проблема была в ошибке разбора, утверждая что он имеет локальный тип Error
. Если этого не произойдет, утверждение типа не будет выполнено, что приведет к ошибке во время выполнения, которая продолжит раскручивать стек, как будто ничего не прерывалось. Эта проверка означает, что если происходит что-то неожиданное, например запрос индекса вне пределов, код не будет работать, даже если мы используем panic
и recover
для обработки ошибок разбора.
С обработкой ошибок на месте, метод error
(потому что это метод, связанный с типом, это нормально, даже естественно, чтобы он имел то же имя что и встроенный тип error
) позволяет легко сообщать об ошибках разбора, не беспокоясь о раскручивании стека разбора вручную:
if pos == 0 {
re.error("'*' недопустимо в начале выражения")
}
Хотя этот шаблон полезен, его следует использовать только внутри пакета. Parse
превращает свои внутренние вызовы panic
в значения error
; он не подвергает panic
своему клиенту. Это хорошее правило для использования в своей практике.
Кстати, эта идиома повторной паники изменяет значение паники, если фактическая ошибка происходит. Тем не менее, как оригинальные, так и новые сбои будут представлены в отчете о сбое, поэтому основная причина проблемы будет все равно видна. Таким образом, этот простой подход повторной паники обычно достаточен - в конце концов, это сбой - но если вы хотите отображать только исходное значение, вы можете написать немного больше кода, чтобы отфильтровать неожиданные проблемы и повторить панику с исходной ошибкой.
Читайте также:
Комментариев нет:
Отправить комментарий