Где размещать объявления интерфейсов в Go проектах: в каждом пакете или в отдельной папке
Что проверяют
Понимаете ли вы, что в Go интерфейсы реализуются неявно, и какие выводы из этого следуют для структуры проекта. Следом обычно спрашивают, как вы мокаете зависимости в тестах и откуда берутся циклические импорты.
Как отвечать
Идиоматичный ответ: интерфейс объявляется там, где его используют, а не там, где реализуют. Так прямо написано в Go Code Review Comments, и это следствие неявной реализации: структуре не нужно знать об интерфейсе и импортировать его пакет.
- на стороне потребителя: сервис
orderобъявляет у себяtype PaymentGateway interface { Charge(ctx context.Context, amount int64) error }ровно с теми методами, которые вызывает. Пакет с реализацией экспортирует конкретный тип; - «принимай интерфейсы, возвращай структуры»: конструктор
NewPostgresRepoвозвращает*PostgresRepo, а не интерфейс, и потребитель сам решает, через какую абстракцию с ним работать; - маленькие интерфейсы: один-три метода, как
io.Reader. Интерфейс на двадцать методов тяжело мокать и почти невозможно реализовать иначе; - исключение: общий контракт, который реализуют многие пакеты, живёт в пакете своей предметной области, как
io.Writerилиdatabase/sql/driver. Это абстракция конкретной области, а не свалка всех интерфейсов проекта.
Отдельный пакет interfaces превращается в узел, от которого зависят все, растёт без границ и провоцирует циклические импорты. Проверка реализации на этапе компиляции делается строкой var _ PaymentGateway = (*StripeClient)(nil), моки генерируются через mockgen из go.uber.org/mock или пишутся руками.
Где спотыкаются
Переносят привычку из Java и C#: объявляют интерфейс рядом с реализацией «на всякий случай», даже если реализация одна и подменять её никто не собирается.
Возвращают интерфейс из конструктора. Потребитель теряет доступ к методам конкретного типа, а добавление метода в интерфейс ломает всех, кто его реализует.
Отвечают «зависит от размера проекта» без критерия. Критерий здесь есть: кто вызывает методы, тот и определяет интерфейс.