Проблемы функции Golang init

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

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

Предположим, что у вас есть код без функции init, например, такой:

// foo.go
package foo

var A int

func bar(){}

// main.go

import "foo"

func main(){
  fmt.Println(foo.A)
}

В данном случае импортируется пакет foo, используется переменная A без других частей. Все в явном виде. У вас может возникнуть вопрос, если я использую только переменную A, могу ли я просто импортировать A без других переменных и функций в этом пакете? Ответ в Golang таков: Нет. Так делать нельзя, необходимо импортировать весь пакет, поскольку он является единицей программирования, которую нельзя разделить. Этот код работал эффективно, пока в игру не вступила функция init.

Пакет, имеющий несколько функций init, может выглядеть следующим образом:

// foo.go
package foo

var A int

func init() { A = 1 }

func bar(){}

// bar.go
package foo

var B int

func init() { B = 2 }

func bar() {}

Как пользователь пакета, ваш код не меняется, в нем по-прежнему используется только переменная A:

// main.go

import "foo"

func main(){
  fmt.Println(foo.A)
}

Пакет по-прежнему работает. Но функция init выполняется неявно, как вы и не знали. В Golang вы должны принять затраты на init, если вы являетесь пользователем пакета. Это просто, но затраты будут не только на неявно выполняемую функцию, но и на весь пакет.

Когда вы пытаетесь написать несколько модульных тестов, вы не можете запретить функцию init. Особенно если вы инициализируете какие-то внешние ресурсы (например, базы данных, файлы, журналы или другие), ваши юнит-тесты сломаются, они должны загрузить ресурсы, даже если вы хотите написать всего лишь маленький юнит-тест.

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

Функция init не была вызвана пользователем пакета, она была вызвана до main. Если в функции init произошла ошибка, что можно сделать? Как использовать единственный механизм обработки ошибок (if err != nil)? Может быть, в ней можно использовать panic, но как использовать recover для обработки этой паники? Как объяснить пользователям пакета, что они должны следить за тем, чтобы пакет не паниковал? Как объяснить, что пакет может запаниковать при запуске, даже если пользователь пакета просто вставил в свой код строчку import?

func init(){
  f, err := file.Open(path) // how to handle the err?
}

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

package foo

import "bar"

func init(){
  bar.Initlization()
}

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

Поразмыслив над проблемами, с которыми я столкнулся в функции init, и прочитав несколько дискуссий об удалении функции init в Go, я пришел к выводу, что лучшей практикой использования функции init является: Не использовать.

Существует несколько способов избежать использования функции init.

Если у вас есть глобальная переменная на уровне пакета, инициализируйте ее при объявлении.

var (
  a = 0
  p *foo = nil
)

Если требуется инициализация ресурсов другого пакета или инициализация каких-то дополнительных ресурсов, используйте экспортируемую функцию Init*.

package foo

var (
  f *os.File
)

func InitFoo(path string) error {
  f, err := file.Open(path)
  _ = f
  return err
}

Если вы хотите, чтобы функция Init* выполнялась только один раз, используйте функцию sync.Once.Do:

package foo

var (
  once sync.Once
  f *os.File
)

func InitFoo(path string) error {
  var err error
  once.Do(func(){
    f, err = os.Open(path)
  })
  return err
}

Если в вашем пакете есть несколько частей ресурса, и вы хотите, чтобы они инициализировались по отдельности, используйте старое и скучное объектно-ориентированное программирование (функции-конструкторы).

// foo.go
package foo

type Foo struct {}

func NewFoo() (*Foo, error) {
  return &Foo{}, nil
}

// bar.go
package foo

type Bar struct {}

func NewBar() (*Bar, error) {
  return &Bar{}, nil
}

Если вы все же хотите использовать функцию init в своем коде, то единственный совет - не размещайте в функции init вызовы других пакетов, даже просто переменных.

Удаление функции init сделает ваш код более прозрачным и менее связанным. Все будет работать явно, затраты будут видны, а ваш код будет простым и легко читаемым.

Источник: https://habr.com/ru/articles/771858/


Интересные статьи

Интересные статьи

Процесс цифровизации охватил фактически уже все сферы общественной жизни. Активное использование цифровых технологий влечет новые процессы в структуре всей системы права. Отношения в данной сфере меня...
В приложениях с REST архитектурой существует ряд проблем:- повторяющийся код при работе с состоянием приложения;- костыли и велосипеды при обработке результатов и состояний запросов;- отсутствие станд...
Всем привет! Меня зовут Петр Солопов, я руководитель отдела фронтенд-разработки в SuperJob. Думаю, многие из вас видели популярную серию картинок в интернете про фронтенд и бэкенд: на бекенде всегда к...
После начала пандемии и режима самоизоляции у бизнеса возникла проблема в виде необходимости обеспечить информационную безопасность сотрудников на удаленке. В самом деле, периметр бе...
Всем привет, меня зовут Лера Лисичкина, я руковожу технологическим пиаром Gett в России. Мы начали рассказывать о нашем московском офисе среди разработчиков не так давно – Gett RnD открылся ч...