В Golang предусмотрен простой интерфейс для ошибок. Любая ошибка, возвращаемая в Golang, следует такому определению интерфейса:

type error interface {
  Error() string
}

Создание сообщения об ошибке в Golang

Простое сообщение об ошибке в Golang создаётся с помощью такого синтаксиса:

package main

import (  
    "errors"
    "fmt"
)

func calculateArea(radius int) (int, error) {  
    if radius < 0 {
        return nil, errors.New("Provide Positive Number")
    }
    return radius * radius, nil
}

В этом коде у нас следующий сценарий: чтобы вычислить площадь круга, нужно убедиться, что радиус, передаваемый в качестве параметра, имеет положительное значение. Если передаваемое значение отрицательно, мы будем возвращать значение площади “0” вместе с объектом пользовательской ошибки из функции. В случае, если передаваемое значение радиуса отрицательно, будем генерировать сообщение о пользовательской ошибке с помощью конструкции errors.New. Эта функция будет содержать сообщение о пользовательской ошибке и создаст объект типа errors. Попробуем вызвать эту функцию с отрицательным значением radius так, чтобы ошибка возвращалась из вызова функции.

package main

import (  
    "errors"
    "fmt"
)

func calculateArea(radius int) (int, error) {  
    if radius < 0 {
        return 0, errors.New("Provide Positive Number")
    }
    return radius * radius, nil
}

func main() {
  areaValue, err := calculateArea(-1);
  if err != nil {
    fmt.Println("Error Encountered...")
    return
  } 
  
  fmt.Println(areaValue)
}

Из вызывающей функции main вызывается функция calculateArea, причём с отрицательным значением параметра. Поскольку передаваемое значение отрицательно, от функции мы будем ожидать объект error. При выполнении функции она возвращает два значения: первое представляет собой найденное значение площади, а второй параметр — объект error. В этом коде мы смотрим, возвращает ли функция объект error или нет. Если в качестве возвращаемого объекта error мы имеем nil, функция продолжает выполнение, в противном случае мы возвращаемся от функции после сообщения об ошибке.

Попробуйте выполнить этот код в специальном редакторе.

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

Ключевые слова defer, panic и recover

Как работает Defer

  • Defer указывает внутренней функции на выполнение перед выходом из внешней функции.
  • Может быть помещена в любое место кода.
  • Мы можем добавить код для освобождения ресурсов в функции defer.
  • Функции с defer выполняются даже при появлении ошибки.

Опробуем всё это на примере:

package main

import "fmt"

func returnMessage() {
  fmt.Println("This is Simple Defer Function Execution")  
}

func main() {
  defer returnMessage()
  fmt.Println("This is line One")
  fmt.Println("This is line Two")
  fmt.Println("This is line Three")
}

Этот код добавляет ключевое слово defer перед вызовом функции returnMessage в потоке main. returnMessage выполняется перед выходом из main:

Даже когда функция returnMessage вызывается в начале кода, она всё равно выполняется в конце выполнения той функции, внутри которой вызвана. Вот как работает в Golang ключевое слово defer. Попробуйте запустить код в редакторе: https://repl.it/@MayankGupta5/usingDefer

Как работает panic

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

  1. Выполнение функции прекращается.
  2. Любая функция, вызываемая ключевым словом defer, выполняется.
  3. Выполнение программы завершается.

Начнём с простой функции panic:

package main

import "fmt"

func executePanic() {
  panic("This is Panic Situation")
  fmt.Println("The function executes Completely")  
}

func main() {
  executePanic()
  fmt.Println("Main block is executed completely...")
}

В этом коде мы вызываем функцию panic в функции executePanic. Как только функция выполняется, программа завершается. Результат выполнения будет таким:

Выход из программы произошёл на строке 6, когда была выполнена функция panic. Таким образом функция panic помогает нам сообщить программе, что имеет место состояние ошибки, и даёт ей указание завершиться с сообщением о ней. Попробуйте запустить код в редакторе: https://repl.it/@MayankGupta5/executePanicKeyword

Работа в тандеме: defer + panic

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

package main

import "fmt"

func recoveryFunction() {
  fmt.Println("This is recovery function...")
}

func executePanic() {
  defer recoveryFunction()
  panic("This is Panic Situation")
  fmt.Println("The function executes Completely")  
}

func main() {
  executePanic()
  fmt.Println("Main block is executed completely...")
}

В этом коде мы добавили ключевое слово defer перед функцией, в которой вызывается функция panic. Результат выполнения будет такой:

В строке 12 появляется ключевое слово panic. При появлении функции panic выполняется функция с defer. Здесь мы видим, что функция defer выполняется до завершения функции. Как только появляется panic, она отыскивает внутри функции все ключевые слова defer и выполняет их до завершения. Опробуем всё это в виртуальном редакторе: https://repl.it/@MayankGupta5/panicdefergo

Работа с recovery

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

Функция defer всегда выполняется при возвращении функции, причём в выполняемой функции ситуация panic может возникать, а может и не возникать. Мы можем указать внутри функции defer сценарий восстановления.

Обнаружение ситуации panic

Мы должны проверить внутри функции defer, возникала ли ситуация panic при выполнении функции. Для этого выполняем функцию восстановления recover. При выполнении recover внутри функции defer мы получаем код сообщения об ошибке, совпадающий со значением параметра, передаваемого в функцию panic. В качестве результата от функции recover возвращается строка, переданная в функцию panic. Это не даёт завершиться выполняемой программе и возвращает контроль над ней. Впоследствии контроль передаётся вызывающей функции, которая продолжает выполнение в обычном режиме. Опробуем всё это на примере:

package main

import "fmt"

func recoveryFunction() {
  if recoveryMessage:=recover(); recoveryMessage != nil {
    fmt.Println(recoveryMessage)
  }
  fmt.Println("This is recovery function...")
}

func executePanic() {
  defer recoveryFunction()
  panic("This is Panic Situation")
  fmt.Println("The function executes Completely")  
}

func main() {
  executePanic()
  fmt.Println("Main block is executed completely...")
}

В этом коде внутри функции defer мы вызываем функцию восстановления recover, которая возвращает сообщение panic, переданное аргументу функции panic. Так как мы используем recover, функция не будет завершаться немедленно: контроль над функцией будет возвращён к вызывающей функции main, а выполнение продолжится в нормальном режиме. Таким образом произойдёт восстановление после ситуации panic.

Результат выполнения будет таким:

Здесь мы видим, что функция не завершается. Она возвращает выполнение в вызывающую функцию main, и дальше выполнение проходит в обычном режиме.

Попробуйте выполнить сами в редакторе: https://repl.it/@MayankGupta5/panicdeferrecover

Заключение

Golang довольно сильно отличается от других языков программирования в том, что касается обработки ошибок. 

А для новичков Golang есть уже готовая интересная статья: Введение в каналы Golang

Читайте также:


Перевод статьи Mayank Gupta: Error Handling in Golang with Panic, Defer and “Recover”

Предыдущая статьяОтладка с Git
Следующая статьяБыстрая сборка и развертывание дашборда со Streamlit