Как стать автором
Обновить

Комментарии 18

про извращение с .Add(-1) только от Вас и узнал, даже не знаю кто так пишет
Применяйте lint, vet и любой статический анализатор, и они вас сами будут бить по рукам за такой код.
Возврат значения неэкспортируемого типа из экспортируемой функции

а почему это плохо? А как ограничить работу со структурой только через конструктор

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


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

Такое использование оправдано в случае, когда конструктор возвращает реализацию какого-то интерфейса. И предполагается дальнейшая работа с этим интерфейсом, а не внутренностями структуры.

Тогда и надо возвращать экспортируемый интерфейс, а не неэкспортируемую структуру. А структура пусть этот интерфейс реализует


И да, я понимаю, иногда очень хочется, чтобы структуру нельзя было создать помимо конструктора. Увы, в Go этого нет.

Если вы говорите про библиотечный код для других, — ок. Если про внутренний код, — то как минимум неоднозначно, но вообще булщит.

У вас есть сервис MakeOrder. Чтобы функционировать ему надо уметь искать юзера по иду. Вы прокидываете зависимость от интерфейса IUserRepository. Интерфейс у вас лежит в пакете сервиса!.. У вас есть пакет адаптера, и там лежит реализация UserRepository. Если конструктор возвращает IUserRepository, то:

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

Если код библиотечный, то конечно возврат интерфейса, ваш код всегда будет деталью, которую надо мочь легко мокнуть.

Описать — интерфейсом. Почему не возвращать интерфейсом — потому что пользователь может хотеть определять более узкий интерфейс на своей стороне.

Ну и пусть описывает более узкий интерфейс. Значение любого интерфейсного типа является assignable переменной более узкого интерфейсного типа.


Например, значение типа net.Conn завсегда можно засунуть в переменную типа io.Writer

А зачем? Теряется весь смысл определения на стороне пакета, соответственно имеет смысл руководствоваться общим правилом интерфейса на стороне потребителя интерфейса.

Что значит — зачем? Чтобы не экспортировать конкретный тип данных.

А как ограничить работу со структурой только через конструктор

Возвращать интерфейс

Самый распространенный антипаттерн разработки: экономить на программистах, экономить на сроках разработки, но ожидать хороший продукт по итогу.

Было бы особенно замечательно, если бы статья раскрывала (да, это статья лишь перевод, но всё же) и такие "антипаттерны", как:


  • Злоупотребление использованием init() { ... } — компилировать в голове и быстро вникать в такой код значительно сложнее, и часто можно написать код без вызовов init() вовсе
  • Злоупотребление экспортируемыми переменными, состояние которых может изменяться из различных мест во время исполнения
  • Не всегда верное понимание интерфейсов в Go — что лучше описывать интерфейсы на принимающей стороне, но в каких случаях есть смысл его описывать на стороне передающей
  • Преждевременная оптимизация участков кода, что выполняются редко или вообще единожды (например — лишь при старте демона) — да, так работает быстрее, но часто этого делать не надо в угоду читаемости и дальнейшей поддержки
  • Недостаточный контроль за зонами ответственности структур/функций/пакетов
  • %место-для-примера-из-вашей-практики%

Почти всё, что описано в статье с лихвой покрывается добрым golangci-lint с правилами "построже", но было бы очень здорово почитать мысли опытных ребят на темы, описанные выше.

Часть умных мыслей по вашим вопросам хорошо описана в книге «Совершенный код» Макконнелл С.

Интересная статья, спасибо за перевод
wg.Add(1)
// ...какой-то код
wg.Add(-1)

Даже в голову никогда не приходило так делать, но спасибо возьму на вооружение.
Хочу сказать спасибо автору за хорошо написанную статью для начинающих! Для тех кто только начинает писать на go она послужит хорошим ориентиром написания хорошего кода.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий