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

В любой программе приходится обрабатывать всякие нестандартные ситуации, ошибки пользовательского ввода, ошибки передачи данных, логические ошибки и др. Давайте рассмотрим процесс обработки ошибок в языке Go. В нем есть два наиболее распространенных способа обработки ошибок. Первый из них - это классический способ обработки ошибок. Следующий шаблон может показать его применение:

result, err := execute()
if err != nil {
   return nil, err
}

Здесь err объект, реализующий интерфейс error, который выглядит следующим образом

type error interface {
    Error() string
}

Простейшая реализация этого интерфейса имеет вид

type errorString struct {
    s string
}

func (e *errorString) Error() string {
    return e.s
}

На первый вгляд все достаточно просто и понятно. Но у этого подхода есть существенный минус. Он достаточно сильно засоряет код объектами err. Плюс он особенно неудобен, когда обработка ошибки осуществляется на несколько уровней выше. Что же делать в таких случаях.

Решение есть. Можно построить механизм обработки ошибок на базе вызовов recover/panic с использованием отложенных вызовов (defer).

panic — это встроенная функция, которая останавливает обычный поток управления и начинает паниковать. Когда функция F вызывает panic, выполнение F останавливается, все отложенные вызовы (defer) в F выполняются нормально (т.е. в порядке LIFO: последний отложенный вызов будет вызван первым), затем F возвращает управление вызывающей функции. Для вызывающей функции вызов F ведёт себя как вызов panic. Процесс продолжается вверх по стеку, пока все функции в текущей горутине не завершат выполнение, после чего аварийно останавливается программа. Паника может быть вызвана прямым вызовом panic, а также вследствие ошибок времени выполнения, таких как доступ вне границ массива.

recover — это встроенная функция, которая восстанавливает контроль над паникующей горутиной. recover полезна только внутри отложенного вызова функции. Во время нормального выполнения, recover возвращает nil и не имеет других эффектов. Если же текущая горутина паникует, то вызов recover возвращает значение, которое было передано panic и восстанавливает нормальное выполнение.

Ниже приведен пример обработки ошибок на базе recover/panic

package main

import "fmt"

func main() {
    f()
    fmt.Println("normal exit from f.")
}

func f() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("recover ", r)
        }
    }()
    fmt.Println("call g")
    g(0)
    fmt.Println("normal exit from g")
}

func g(i int) {
    if i > 3 {
        fmt.Println("panic!")
        panic(fmt.Sprintf("%v", i))
    }
    defer fmt.Println("defer in g", i)
    fmt.Println("print in g", i)
    g(i+1)
}

Результатом выполнения программы будет следующий вывод на консоль

сall g
print in g 0
print in g 1
print in g 2
print in g 3
panic!
defer in g 3
defer in g 2
defer in g 1
defer in g 0
recover 4
normal exit from f.

Если мы уберем отложенный вызов функции f, то паника не останавливается и достигает верха стека вызовов горутины, после чего программа будет остановлена с выводом трассировки.

В итоге при помощи panic/recover можно организовать аналог исключений в других языках программирования.

 

Тэги: go golang программирование


 


 
архив

подписка