На этом шаге продолжим рассматривать использование семейства пользовательских типов в Go на примере. Рассмотрим вспомогательные функции для организации корректной работы приложения.
1. Определение неэкспортируемых переменных для хранения ссылок на вспомогательные функции.
/* Три неэкспортируемые переменные, каждая из которых принимает значение типа int и возвращает значение типа int */ var saneLength, saneRadius, saneSides func(int) int /* Для данного пакета реализована функция init(), где переменным присваиваются ссылки на соответствующие анонимные функции */ func init() { saneLength = makeBoundedIntFunc(1, 4096) saneRadius = makeBoundedIntFunc(1, 1024) saneSides = makeBoundedIntFunc(3, 60) }
2. makeBoundedIntFunc(minimum, maximum int) func(int) int - функция, которая возвращает другую функцию, возвращающую некоторое указанное значение.
/* Функция возвращает другую функцию, возвращающую указанное значение x, если оно находится между значениями minimum и maximum (включительно), или ближайшее граничное значение. Если значение x находится за границами диапазона, функция не только возвращает допустимое альтернативное значение, но еще и регистрирует проблему в журнале. Однако в сообщении об обнаруженной проблеме не должно фигурировать имя функции, созданной здесь (то есть saneLength(), saneRadius() или saneSides()), потому что фактически проблема рождается в вызывающих их функциях. Поэтому вместо имени функции, созданной здесь, в журнал выводится имя вызывающей функции, которое возвращает функция caller(). */ func makeBoundedIntFunc(minimum, maximum int) func(int) int { return func(x int) int { valid := x switch { case x < minimum: valid = minimum case x > maximum: valid = maximum } if valid != x { log.Printf("%s(): изменена %d с %d\n", caller(1), x, valid) } return valid } }
func caller(steps int) string { name := "?" /* Функция runtime.Caller()возвращает информацию о функции, которая была вызвана в текущей go-подпрограмме, но еще не вернула управление. Ее аргумент типа int сообщает, на сколько шагов (то есть функций) назад следует заглянуть. При значении 0 аргумента возвращается информация о текущей функции (то есть о самой функции caller()), при значении 1 возвращается информация о вызвавшей ее функции и т. д. Здесь к значению аргумента добавляется 1, чтобы сразу начать с вызывающей функции. Функция runtime.Caller() возвращает четыре значения: - программный счетчик (сохраняется в переменной pc), - имя файла, где произошел вызов (значение игнорируется), - номер строки, где произошел вызов (значение игнорируется), - логический флаг (сохраняется в переменной ok), сообщающий об успешной или неудачной попытке извлечения информации. */ if pc, _, _, ok := runtime.Caller(steps + 1); ok { name = filepath.Base(runtime.FuncForPC(pc).Name()) } return name } /* В случае успеха программный счетчик передается функции runtime.FuncForPC(), возвращающей значение типа *runtime.Func, которое затем передается методу runtime.Func.Name() для получения имени вызывающей функции. Возвращаемое имя имеет вид пути к файлу, например: /Go/goproject/src/shaper1/shapes.FilledRectangle – для функции, или /Go/goproject/src/shaper1/shapes.*shape.SetFill – для метода. В нашем проекте путь к файлу не представляет интереса, поэтому он отбрасывается с помощью функции filepath.Base(). После этого имя возвращается вызывающей программе. */
На следующем шаге продолжим рассматривать пример использования семейства пользовательских типов в Go.