В Golang, благодаря появлению целого ряда новых средств и инструментов, которыми не могут похвастать другие языки серверного программирования, реализована очень мощная и гибкая модель функционального программирования. Одним из таких нововведений является функция отложенного вызова Defer
.
Для тех, кто только знакомится с Golang, возможно, будет полезно почитать сначала эти статьи:
Итак, рассмотрим функцию отложенного вызова defer, в частности, с точки зрения гибкости, которую они придают приложению. Характеристики defer:
Представим такой сценарий, где пользователь пытается получить доступ к файловой системе. Ему нужно открыть файл и прочитать, что в нём находится, а также выполнить ряд действий над его содержимым. Открыв подключение к файлу и прочитав его содержимое, нужно закрыть подключение.
Для выполнения нескольких задач можно было бы воспользоваться сложными функциями с множеством ключевых слов return
внутри каждой из них. При возврате из функции может потребоваться выполнить кое-какие задачи по очистке.
К числу таких задач могут относиться:
Каждый разработчик будет стремиться к тому, чтобы такие очистки проводились до завершения функции. При этом могут возникать неожиданные ситуации, когда функция возвращает значение в обход обычного маршрута завершения. Во всех этих ситуациях нужен механизм, обеспечивающий выполнение очистки. Работая в сложных приложениях реального времени, разработчик может упускать места и сценарии, где требуется код, связанный с очисткой. Давайте обратимся к конкретному примеру, чтобы разобраться во всём этом.
Ссылка здесь.
Здесь мы пытаемся получить доступ к содержимому файла. После его прочтения нужно закрыть подключение к файлу. Посмотрите на большую функцию, выполняющую несколько действий над поступающими данными. В соответствии с данными от файловой системы, она определяет внутри функции множественный «возврат». Прежде чем выходить из функции, нужно убедиться, что файл закрыт. Так что file.Close()
пришлось бы вставлять сразу в несколько частей кода. Посмотрите на следующий код, чтобы было понятнее.
func GetFileContent() string {
file, _ := os.Open("./FileContent/sampleFile.txt")
data := make([]byte, 100)
file.Read(data)
dataString := string(data)
fmt.Printf(dataString)
if(dataString == "Mayank") {
file.Close()
return "Mayank";
} else {
file.Close()
return "Anshul"
}
}
Внутри этой функции у нас множественный возврат. А это значит, что файл придётся закрывать в нескольких местах и сложность кода будет только увеличиваться. Разработчику надо быть внимательным: из-за усложнения кода он может упустить ту его часть, где требуется освобождение ресурсов.
Итак, рассмотренный нами код описывает сценарий, который ведет к усложнению программы. Но как может помочь решить эту проблему функция с defer
? Любая вызываемая функция с ключевым словом defer
будет выполнена до завершения текущей функции. Код для добавления функции defer
очень простой:
func InvokeDeferFunction() {
defer DeferFunctionCall()
fmt.Println("Defer Function Still Not Called...")
}
func DeferFunctionCall() {
fmt.Println("Defer Function is called after function call...")
}
Здесь важны два пункта:
defer
добавляется перед DeferFunctionCall()
.defer
добавляется в начало выполнения функции.Вывод такой:
Несмотря на то, что функция defer
добавлена в начало, она не была выполнена: она выполняется, когда завершается текущая функция. Функция с defer была вызвана перед выходом из текущей функции InvokeDeferFunction
. Мы можем использовать эту особенность и расположить в стеке функции отложенного вызова, которые будут выполняться перед выходом. Можете поэкспериментировать с этим по ссылке.
Таких функций defer
, выполнение которых должно произойти до завершения текущей функции, может быть несколько. Причём у каждой из них будут свои особенности. Давайте включим в код несколько таких функций defer
:
package main
import "fmt"
func main() {
InvokeDeferFunction()
}
func InvokeDeferFunction() {
defer DeferFunctionCall()
defer OtherDeferFunctionCall()
fmt.Println("Still executing InvokeDeferFunction")
}
func DeferFunctionCall() {
fmt.Println("Defer Function Called...")
}
func OtherDeferFunctionCall() {
fmt.Println("Other Defer Function called...")
}
Здесь несколько таких функций defer
, как только завершается текущая функция, располагаются в стеке, организованном по принципу LIFO: последняя добавленная функция будет первой на выполнение. Вот какой вывод будет у этого кода:
Можно отметить два обстоятельства:
defer
.defer
, добавленная в конец, будет вызвана первой.Поэкспериментировать предлагаю в этом виртуальном редакторе:
Ссылка здесь
Вернёмся к нашему первому примеру с чтением файла. Теперь мы можем переписать код, используя функцию defer
. Посмотрите, как выглядит код в этом случае:
func GetFileContent() string {
file, _ := os.Open("./FileContent/sampleFile.txt")
defer file.Close()
data := make([]byte, 100)
file.Read(data)
dataString := string(data)
fmt.Printf(dataString)
if(dataString == "Hello Mayank") {
return "Mayank";
} else {
return "Anshul"
}
}
Мы добавили функцию отложенного вызова, которая будет вызывать выполнение кода для закрытия подключения к файлу. В функции есть ключевое слово defer
, поэтому она будет вызвана в конце. Не важно, по какому пути выполнения следует эта функция: функция закрытия файла всегда будет выполняться.
При этом код будет лёгким, так как:
defer
могут быть свои задачи в коде. Одна функция defer
может заниматься извлечением файловых ресурсов, другая — закрытием сетевого подключения. Так что у нас здесь что-то вроде разделения труда.Возможно, важнейшая особенность ключевого слова defer, помогающая пользователям справляться с ошибками. Обработка ошибок производится с использованием следующих ключевых слов:
Интересно, как с помощью ключевого слова defer
происходит обработка ошибок в Golang? Подробно ознакомиться с этим можно в следующей статье:
Обработка ошибок в Golang с помощью Panic, Defer и Recover
Существует также много других средств и инструментов, которые могут использовать преимущества функции defer
и добавить мощи программам на Golang. Надеюсь, вам понравилась статья.
Перевод статьи
Комментарии