Комментарии 18
Возврат значения неэкспортируемого типа из экспортируемой функции
а почему это плохо? А как ограничить работу со структурой только через конструктор
Потому, что это чертовски неудобно для того, кто будет таким пакетом пользоваться.
Например, поместить результат такой функции в переменную можно, воспользовавшись автовыводом типов. А в поле структуры уже не поместишь; непонятно, как такое поле описать. И другой функции в качестве параметра не передашь, по аналогичной причине.
Тогда и надо возвращать экспортируемый интерфейс, а не неэкспортируемую структуру. А структура пусть этот интерфейс реализует
И да, я понимаю, иногда очень хочется, чтобы структуру нельзя было создать помимо конструктора. Увы, в Go этого нет.
У вас есть сервис MakeOrder. Чтобы функционировать ему надо уметь искать юзера по иду. Вы прокидываете зависимость от интерфейса IUserRepository. Интерфейс у вас лежит в пакете сервиса!.. У вас есть пакет адаптера, и там лежит реализация UserRepository. Если конструктор возвращает IUserRepository, то:
- Вы создаете ненужную жесткую связку с пакетом сервисов. Ненужная, потому что интерфейсы удовлетворяются неявно.
- Вы ограничиваете использование репозитория одним интерфейсом, хотя он мог бы удовлетворять нескольким, опять же из за неявности.
- При написании юнит теста для репозитория вы либо будете использовать ваш дурацкий конструктор и тогда будете сильного ограничены в том, что вы можете протестировать, либо будете дублировать логику создания структуры чтобы мочь затестить все что нужно.
Если код библиотечный, то конечно возврат интерфейса, ваш код всегда будет деталью, которую надо мочь легко мокнуть.
Описать — интерфейсом. Почему не возвращать интерфейсом — потому что пользователь может хотеть определять более узкий интерфейс на своей стороне.
А как ограничить работу со структурой только через конструктор
Возвращать интерфейс
Было бы особенно замечательно, если бы статья раскрывала (да, это статья лишь перевод, но всё же) и такие "антипаттерны", как:
- Злоупотребление использованием
init() { ... }
— компилировать в голове и быстро вникать в такой код значительно сложнее, и часто можно написать код без вызововinit()
вовсе - Злоупотребление экспортируемыми переменными, состояние которых может изменяться из различных мест во время исполнения
- Не всегда верное понимание интерфейсов в Go — что лучше описывать интерфейсы на принимающей стороне, но в каких случаях есть смысл его описывать на стороне передающей
- Преждевременная оптимизация участков кода, что выполняются редко или вообще единожды (например — лишь при старте демона) — да, так работает быстрее, но часто этого делать не надо в угоду читаемости и дальнейшей поддержки
- Недостаточный контроль за зонами ответственности структур/функций/пакетов
- %место-для-примера-из-вашей-практики%
Почти всё, что описано в статье с лихвой покрывается добрым golangci-lint с правилами "построже", но было бы очень здорово почитать мысли опытных ребят на темы, описанные выше.
wg.Add(1)
// ...какой-то код
wg.Add(-1)
Даже в голову никогда не приходило так делать, но спасибо возьму на вооружение.
Go: распространенные антипаттерны