Куда нужно положить интерфейс, если робот реализует интерфейс?
Что проверяют
Знаете ли вы главную идиому Go: интерфейс объявляет тот, кто им пользуется, а не тот, кто его реализует. Вопрос отсеивает кандидатов, которые перенесли в Go привычки Java и C#. Следом обычно просят объяснить, почему это работает и когда правило нарушают.
Как отвечать
В Go реализация интерфейса неявная: тип удовлетворяет интерфейсу, если у него есть нужные методы, и пакету с типом не нужно этот интерфейс импортировать. Отсюда правило из Go Code Review Comments: интерфейс кладут в пакет, который принимает значения этого типа.
На примере с роботом:
- пакет
robot: экспортирует конкретную структуру и конструкторfunc New() *Robot. Интерфейса здесь нет, возвращается конкретный тип; - пакет-потребитель, например
warehouse: объявляетtype Mover interface { Move(ctx context.Context) error }ровно с теми методами, которые ему нужны, и принимаетMoverв конструкторе; - связывание: в
mainили в пакете сборки зависимостейrobot.New()передаётся туда, где ждутMover; - тесты: мок для
Moverгенерируетсяmockgenилиmoqрядом с потребителем, и пакетrobotдля тестовwarehouseвообще не нужен.
Короткая формула: accept interfaces, return structs. Маленький интерфейс из одного-двух методов потребитель описывает сам.
Назовите исключения. Если интерфейс это общий контракт с множеством реализаций, его держат рядом с контрактом: io.Reader в io, driver.Driver в database/sql/driver.
Проверку на этапе компиляции, var _ warehouse.Mover = (*robot.Robot)(nil), пишут в тестах или в пакете сборки, чтобы не связывать пакеты лишним импортом.
Где спотыкаются
Кладут интерфейс Robot рядом со структурой и возвращают его из конструктора. Потребитель получает все методы сразу, а каждое изменение интерфейса ломает моки во всех пакетах, даже если им нужен один метод.
Делают один большой интерфейс на двадцать методов «на всякий случай». Для теста одного вызова приходится реализовывать все двадцать.
Заводят отдельный пакет interfaces или contracts на весь проект. Он становится общей зависимостью и провоцирует циклические импорты, которые Go не компилирует.