Конструкторы для бедных в Go
Давайте поговорим про Make zero value useful принцип в Go.
Мне кажется это хороший принцип, он отлично работает во многих случаях, и при этом не усложняет язык конструкторами, порядками их вызовов и тд.
Но сделать zero value useful иногда не так просто. Самый банальный пример это когда Вам надо сделать map одним из полей Вашей структуры и потом записывать туда какие-то значения.

Если использовать эту структуру как есть, то получим panic: assignment to entry in nil map

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

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

Но это тоже не идеально, ведь этот инициализационный метод можно просто забыть вызвать.
Ну тут велосипед придумывать не обязательно, можно создать метод New, и в документации к типу и этому методу описать что Repo можно создавать только используя New.

Любой опытный Go программист видел такие методы уже тысячи раз и всегда ищет их когда начинает использовать новую библиотеку или тип. И на этом можно завершать, ведь все люди читают документацию к типу, перед тем как его использовать. Даже если кто-то ошибся, то буквально при первом же использовании типа этот человек получит панику и быстренько разберется что случилось.
И хоть я считаю что методов New... должно хватить для 99% случаев, надо признать что бывают случаи, когда допускать использование неинициализированных объектов и перекладывать всю ответственность на пользователя как-то не очень хорошо. Вот парочка примеров которые я придумал сходу:
- Неинициализированный ресурс/поле используется редко или в каких-то граничных условиях и пользователь может даже не обнаружить возможную панику на этапе тестирования.
- Мы разрабатываем библиотеку с повышенными требованиями к отказоустойчивости, например она может использоватся в области медицины или банковском приложении, допускать паники в таких условиях не стоит особенно если их можно избежать.
Что же в таких случаях нам ничего не остается как использовать init методы в начале любой операции? Наверное есть еще один вариант который я редко где встречал и он мне кажется довольно очевидным и легким в использовании. Выглядит это так:


Тут получается что наш защищенный тип safeRepo напрямую создать нельзя и клиенты всегда используют некий тип обертку который будет безопасно вызывать без инициализации. Do метод сам по себе будет инлайнбл и компилятор, по идее, должен довольно легко убирать лишние проверки на инициализацию.
Еще Do может не только инициализировать ресурсы но и проводить валидацию состояния защищенного типа и многое другое.
Но просто так возвращать ссылку на приватный тип safeRepo - это не самая хорошая идея. Проблема тут в том что пользователи нашего пакета не смогут у себя определить interface который имплементирует тип Repo. А если попытаться так сделать

то получим cannot refer to unexported name repo.safeRepo.
Не для всех это будет проблемой, но если надо, то обойти это ограничение можно определить interface защищенного типа у нас в пакете и возвращать уже его, хоть это и расходится немного с философией языка.
