«Go»: обработка ошибок

После первого прочтения документации по языку «Гоу» у меня сложилось неправильное впечатление о конструкции «panic», отчасти из-за её названия. Мне показалось, что «panic» используется для генерирования фатальной, для программы, ошибки, которую лучше не обрабатывать, а прекратить выполнение, во избежание серьёзных проблем.

Моё небольшое исследование показало, что это не так, «panic» — это стандартное средство генерации ошибки (аналог исключений), тогда как «defer» плюс «recover» — стандартный способ сгенерированную ошибку отловить и обработать.

Напомню как это делается. Генерация ошибки осуществляется вызовом «panic», перехват — defer-вызовом функции, в которой при помощи функции recover проверяется был ли defer-вызов произведён из-за вызова «panic» (defer вызывается, когда управление, по любой причине, покидает текущую зону видимости).

Например:

package main

func test() int {
    defer func() {
        if e := recover(); e != nil {
            print("Error\n")
        }

        panic(true)
}

func main() {
   print(test())
}

Ошибки могут быть типизированными: можно вызвать «panic(42)», «panic(„Hello“)», «panic(struct{h, k int}{0, 0})» или, к примеру, «panic(os.NewError(„Cannot open file“))», тип ошибки можно узнать, так же как и её содержимое. Это уже больше похоже на исключения.

Если внимательно посмотреть на мой пример выше, то не совсем ясно следующее: «паника» привёла к выполнению анонимной defer-функции и, фактически, прервала выполнение функции «test». Эта функция была описана, как возвращающая результат типа «int». Что нужно сделать, чтобы в случае вызова «panic», мы смогли вернуть какой-нибудь результат?

Способ есть, давайте разберём его в другом примере. В нём же я покажу как именно определить тип «паники».

package main

import . "os" // содержимое модуля будет доступно без префикса

func poisson(n int) (result string) {
    defer func() {
        switch r := recover(); r.(type) {
            case string:
                result = "Result: " + r.(string) + "\n"
            case Error:
                result = "Error: " + r.(Error).String() + "\n"
        }
    }()

    if n < 0 {
        // NewError и Error используется из модуля «os»
        panic(NewError("Poisson distributions have no value over negative numbers"))
    }

    panic("I dunno")
}

func main() {
    print(poisson(-1)) // Poisson distributions have no value over negative numbers
    print(poisson(+1)) // I dunno
}

У функции «poisson», которая притворяется, что считает распределение Пуассона, возвращаемый параметр имеет имя, он как бы связан с переменной «result». Если мы что-то поместим в эту переменную, то это и будет возвращаемым функцией значением. Поскольку в «Go» есть замыкания, мы можем использовать переменную «result» и внутри анонимной функции в defer-вызове. Всё просто.

Что происодит дальше. Вызов «panic» принимает на вход «interface{}», то есть, фактически, параметр любого типа. Чтобы определить тип этого параметра, используется специальнуя конструкция «переменная.(type)» (которая, к слову, может использоваться только в операторе «switch»).

Упомянутая конструкция может определить какой тип ошибки нам вернулся. Похожая конструкция «переменная.(имя-типа)» („r.(string)“ и „r.(Error)“) называется «type assertion». Она позволяет, в общих чертах, проверить, что в переменной сохранено значение указанного типа и получить это значение.

Полученные значения обрабатываются и сохраняются в переменной «result», которая связана со значением возвращаемым функцией «test», оно и попадает в print функции «main».

Я уже настолько проникся языком, что это решение уже мне не кажется таким странным, как поначалу.

4 комментария

Деревня Верхние Девлизери. Троицкий приход

Примерно в часе езды от Казани находится деревня Верхние Девлизери (Пестречинский район), исчезающе маленькая. Рядом находится Троицкий приход, от которого осталась потрясающе красивая церковь 1904 года, несколько разрушенная, но хорошо сохранившаяся. Внутри ещё различимы фрески, мы сегодня в ней побывали.

Разрушенная кирпичная церковь с колокольней на фоне ясного неба, окружённая полем и электропроводами.
Вид на верхнюю часть старинной кирпичной колокольни с облупившейся белой штукатуркой, снятый снизу через чёрную металлическую решётку на фоне ясного голубого неба.
Фрагмент ветхой кирпичной стены с арочными окнами в кованых решётках и открытой резной дверью, перед которой растёт кустарник на фоне поля.
Фотография разрушенной кирпичной церкви с арочными окнами, обветшалыми ставнями и травой на крыше под ясным голубым небом.
Фрагмент фрески с изображением группы людей в античных одеждах, частично разрушенной: фигуры склонены, одна держит ветвь, другая — сосуд, фон покрыт белыми пятнами отслоений.
Фрагмент фрески с изображением святого в красном облачении и зелёной мантии, держащего книгу; лицо и надпись сильно повреждены, видны следы времени и разрушения.
Износостойкая фреска на стене изображает монаха в чёрной рясе с крестом, окружённого облупившейся краской и граффити.
Фрагмент росписи на арке: в центре — православный крест в медальоне с цветами, по бокам — орнаментальные завитки; штукатурка облупилась, видна кирпичная кладка.
Фрагмент старинной росписи на колонне с растительным орнаментом из завитков и листьев, частично утраченной из-за отслоения штукатурки.
Потолок с выцветшей росписью в виде симметричного орнамента из синих и коричневых завитков и листьев на фоне облупившейся штукатурки.
Фрагмент старой стены с облупившейся краской и выцветшей росписью в виде стилизованного растительного орнамента на сером фоне, рядом видна часть окна с решеткой.
Фрагмент старинной росписи на потолке с выцветшим орнаментом из завитков, листьев и полумесяца на розовом фоне с облупившейся краской.

Эта каменная церковь была построена на средства мещанина г. Алатыря Василия Львовича Симбирина, по образцу церкви в селе Анатьли Рыбнослобоцкого района. В 1905 году церковь освятили, а в 1908 получила статус церковно-приходского совета, при церкви была открыта земская школа.

Оставить комментарий