В Golang предусмотрен простой интерфейс для ошибок. Любая ошибка, возвращаемая в Golang, следует такому определению интерфейса:
type error interface {
Error() string
}
Простое сообщение об ошибке в 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
указывает внутренней функции на выполнение перед выходом из внешней функции.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
:
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
Всякий раз, когда функция 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
При появлении ситуации panic программа завершается. Завершение работы реального приложения на ошибке — это не самое лучшее, что может быть. Нужен какой-то механизм восстановления после возникновения ошибки. В ситуации panic хотелось бы иметь некий код восстановления, помогающий избежать нежелательного завершения программы.
Функция defer
всегда выполняется при возвращении функции, причём в выполняемой функции ситуация panic может возникать, а может и не возникать. Мы можем указать внутри функции defer
сценарий восстановления.
Мы должны проверить внутри функции 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”
Комментарии