Только не экспортируй приватные типы в Go

При работе с Go разработчики нередко сталкиваются с типичной ошибкой проектирования — экспортом приватных типов. На первый взгляд она может показаться безобидной, но в долгосрочной перспективе приводит к архитектурным ограничениям и усложняет сопровождение кода. В этой статье мы объясним, почему стоит избегать этого антипаттерна, откуда он возник и какие проблемы он создаёт в реальных проектах.
28 мая 2024 г. — Примерное время чтения: 8 минут

Неэкспортируемые типы в Go: откуда все началось

При переходе в мир Go из классических объектно-ориентированных языков, таких как Java или PHP, многие разработчики привыкли к использованию классов и автоматической инициализации объектов при вызове конструктора.

Но структуры в Go – это не то же самое, что классы и объекты в других языках. Они играют похожую роль, но не такую же!
Кстати, узнайте больше о нашем подходе в веб-разработке, также рекомендуем посмотреть наши кейсы – они точно вас удивят.

Проблемы с экспортом приватных типов

Основная проблема с экспортированием приватных типов заключается в том, что это нарушает принцип инкапсуляции.

Само название "неэкспортируемая структура" говорит: она для внутреннего использование пакета. И нужна, чтобы потребители кода работали ТОЛЬКО с экспортируемыми типами. Это инкапсуляция, которая позволяет скрывать то, куда не надо лезть извне! То, что работает сложным образом, а также может изменяться с новыми версиями пакета. Поэтому делать такое - то же самое, что пытаться сделать что-то маркируемое "несъедобное" - съедобным. Это забивать гвозди перфоратором. Если программист так делает, то он просто не понимает зачем это нужно.

Написание структур, которые не требуют инициализации.

Примеры: sync.Mutex, time.Time, sync.WaitGroup и другие.

При дефолтной инициализации они будут работать корректно. Можно использовать именованные конструкторы для инициализации в нужное состояние: time.Now(), time.Parse(). Это идеальный подход! Но такого можно достичь не во всех случаях.

Сложный доменный сервис может потребовать более глубокой инициализации

Например, инъецировании зависимостей (Config, Logger, Repo, NetClient и т.п.). В таком случае создаётся именованный конструктор и структура с приватными полями, в которые вообще ничего нельзя записать при базовой инициализации. В этом случае зависимости, конфигурация и прочее проверяются в конструкторе, который может вернуть ошибку.

NewDomainService(c Config, d Dependencies) (*DomainService, error)

У таких сервисов в сигнатуре всегда будут методы, которые выполняют какую-то доменную функцию:

  • Start(ctx context.Context) error
  • Process(ctx context.Context) error
  • Execute(ctx context.Context) error
  • .....

Если сервис не был корректно инициализирован, то они вернут ошибку. В 99% случаев такие сервисы и так могут вернуть разные ошибки по десяткам причин. Так что аргумент "мне лень обрабатывать ошибки" - звучит плохо.

Обрабатывать ошибки – это подход Go.

Это делается двумя строчками, которые генерируются IDE автоматически уже. И именно это позволяет сразу понимать узкие места своего кода, возможные ошибки и заставляет думать что с ними делать. И ничего страшного при этом не случиться.

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

Если же подобная проблема (мой сервис не был инициализирован) приводит к "каким-то страшным критическим вещам", например, удалилась база данных, отформатировался диск, перевелись биткоины в несуществующий кошелёк, то у вас проблемы с написанием сервисом! Проблема в Вас, а не в том, кто использует ваш код! Потому что так случаться никогда не должно!

Защита от неведомых проблем с "глупыми потребителя" – жди проблем

А именно:

  1. Вы не сможете построить нормальную архитектуру приложения. Потому что вы просто не можете использовать этот приватный тип в слоях верхнего уровня. Например, если это репозиторий, или какой-то другой доменный сервис, то его надо описать в интерфейсе потребителя. Но вы не можете этого сделать, потому что это приватный тип! Вы просто не сможете им типизировать что-то за пределами его пакета! Поэтому DI работать не будет.
  2. Если вы хотите подсунуть его в интерфейсный тип какого-то потребителя, то вам понадобится следующее:

    -ServiceLocator или ApplicationRegistry - это объект верхнего уровня архитектуры, который хранит ссылки на основные сервисы. Он должен знать хранить инициализацию этого объекта.

    -IoС - тут будет необходимость забиндить реализацию для интерфейса, который будет автоматически инъецироваться при билдинге объектов. Будете ли вы это делать руками, использовать Wire или FX, вам всё равно надо написать, например, FX Show конструктор для этого типа, а потом забиндить его к нужным интерфейсам. Всё это невозможно сделать с приватными типами.
  3. Вы не сможете использовать документацию GoDoc, потому что приватные типы там не описываются для внешнего использования, что абсолютно логично!
  4. Противоречит идиоматике языка и путает других разработчиков нестандартным и неправильным подходом.

Вместо заключения

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

Нужна помощь с Go-разработкой или оптимизацией приложения? Свяжитесь с нами — подскажем, как решить задачу максимально эффективно.

Другие статьи автора

SEO — это инвестиция. Вот сколько она приносит SEO — это инвестиция. Вот сколько она приносит

SEO в России и СНГ — это не трата, а реальный способ развивать бизнес онлайн. Когда реклама становится всё дороже, органическое продв...

2 мая 2025 г.
Ошибки в карте сайта Sitemap.xml, которые убивают ваш SEO-трафик Ошибки в карте сайта Sitemap.xml, которые убивают ваш SEO-трафик

Карта сайта (Sitemap.xml) — это как навигатор для поисковиков. Если всё настроено правильно, она помогает сайту быстрее расти в поиск...

2 мая 2025 г.
"Просто добавить блок"? Почему это не стоит $10 и не делается за 5 минут "Просто добавить блок"? Почему это не стоит $10 и не делается за 5 минут

Вы приходите к разработчику и говорите: «Нужно просто вставить маленький блок. Всего пару строк текста, что тут сложного?» Кажется, ра...

3 апреля 2025 г.
Фикс или почасовая оплата: как выбрать выгодный формат для бизнеса? Фикс или почасовая оплата: как выбрать выгодный формат для бизнеса?

Вы запускаете проект, и подрядчик предлагает почасовую оплату. А вы хотите фикс — понятную сумму без неожиданностей. Всё логично: биз...

1 апреля 2025 г.

Похожие статьи

Безопасность веб-приложений - современные подходы Безопасность веб-приложений - современные подходы

В современной разработке веб-порталов вопрос безопасности выходит на первый план. Сегодня защита данных пользователей и устойчивость в...

28 мая 2024 г.
Как автоматизация бизнеса повышает прибыль и экономит время на решение рутинных задач и бухгалтерских вопросов Как автоматизация бизнеса повышает прибыль и экономит время на решение рутинных задач и бухгалтерских вопросов

Как автоматизация бизнеса влияет на управление сайтом, эффективность и прибыль компании? Автоматизация рутинных процессов и клиентског...

2 февраля 2024 г.
Темная сторона контент менеджмента Темная сторона контент менеджмента

Что такое контент-менеджмент? Проще говоря, это системная работа по развитию сайта: от планирования публикаций до наполнения его полез...

19 ноября 2024 г.
cookies Мы используем Cookie

Мы используем файлы Cookie для улучшения работы сайта, персонализации контента и анализа трафика. Вы можете выбрать, какие категории Cookie разрешить. Подробнее читайте в нашей Политике Cookie. Вы можете изменить свой выбор в любое время.

Необходимые (обязательные)

Обеспечивают работу сайта (навигация, доступ к защищённым разделам). Всегда включены и могут быть изменены только в настройках браузера.

Аналитические

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

Маркетинговые / Рекламные

Используются для показа персонализированной рекламы и измерения эффективности рекламных кампаний.

На этом сайте мы дополнительно используем пиксель OpenAI для ChatGPT Ads. Он сохраняет в браузере идентификатор __oppref. При отправке заявки ваш e-mail и номер телефона могут быть переданы в OpenAI в США в хешированном виде, чтобы связать заявку с рекламой, по которой вы перешли. В читаемом виде они не передаются.