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

Рекомендуем вам супер TELEGRAM канал по Golang где собраны все материалы для качественного изучения языка. Удивите всех своими знаниями на собеседовании! 😎
Мы публикуем в паблике ВК и Telegram качественные обучающие материалы для быстрого изучения Go. Подпишитесь на нас в ВК и в Telegram. Поддержите сообщество Go программистов.
В этой статье я поделюсь сочетанием рекомендации по мышлению и практических советов, которые, надеюсь, помогут вам, особенно если вы новичок в языке.
- Разные проекты, разные структуры
- Стремитесь к эффективности, а не к совершенству
- Забудьте о соглашениях из других языков или фреймворков
- Не используйте директории только для организации файлов
- Используйте одну из стандартных структур в качестве скелета
- … А затем позвольте ей эволюционировать
- Если вы не уверены, начните с двух файлов
- Держите связанные вещи рядом
- Большие файлы не обязательно плохи
- Создавайте пакеты обдуманно
- Обращайте внимание на предупреждающие знаки
1. Разные проекты, разные структуры
Хочу начать с подчеркивания того, что не существует единственного «правильного» способа структурировать кодовую базу Go.
Если вы используете определенный фреймворк или инструмент для создания проекта, то вам может быть предоставлена фиксированная структура директорий. Но помимо этого, в сообществе Go относительно мало широко принятых соглашений, и ответ на вопрос «как мне структурировать мою кодовую базу?» почти всегда звучит как «это зависит от обстоятельств».
Это зависит от того, что вы создаете, ваших бизнес-потребностей, подхода к тестированию, вашей команды, зависимостей или инструментов, а также от любых внутренних соглашений, которым вы решили следовать.
Посмотрите на GitHub, и вы найдете тысячи примеров успешных проектов на Go — с совершенно разными структурами. Например, mkcert и Kubernetes — оба отличные проекты на Go, но они значительно отличаются по масштабу и назначению. И эти различия означают, что структуры их репозиториев также выглядят совершенно по-разному.
Структура, которая хорошо работает для вашего текущего проекта, может отличаться от структур, которые вы использовали раньше или видели в других местах — и это совершенно нормально.
2. Стремитесь к эффективности, а не к совершенству
Если вы перфекционист, это может быть легче сказать, чем сделать, но постарайтесь не слишком переживать о создании идеальной структуры кодовой базы.
Если вы обнаружите, что одержимы поиском «идеального» способа организации кода, попробуйте отпустить эту мысль. Вместо этого стремитесь к структуре, которая работает достаточно эффективно для вашего конкретного проекта. Под «достаточно эффективно» я подразумеваю, что ваш код легко найти и в нем удобно ориентироваться, логика проста для понимания, изменения можно вносить с уверенностью, и вы не сталкиваетесь с теми предупреждающими знаками, о которых я расскажу позже в этой статье.
3. Забудьте о соглашениях из других языков или фреймворков
Не чувствуйте себя виноватым, если структура вашей кодовой базы не следует соглашениям или лучшим практикам, к которым вы привыкли в других языках или фреймворках. Если она эффективно работает для вашего проекта на Go, это и есть главное.
Например, если вы опытный разработчик Ruby on Rails или Django, создающий свое первое веб-приложение на Go, у вас может возникнуть соблазн воссоздать знакомую структуру директорий из этих фреймворков. Но хотя вы, вероятно, могли бы заставить это работать, если бы попытались, это вряд ли будет наиболее эффективным или простым решением для вашего проекта на Go.
4. Не используйте директории только для организации файлов
Это тонкий, но важный момент, особенно если вы новичок в Go.
Вы не должны создавать новые директории только для организации ваших файлов .go
. В Go создание директории создает новый пакет, а помещение файла в эту директорию делает его частью этого пакета.
Создавайте директорию только тогда, когда у вас есть конкретная причина для создания нового пакета – а не потому, что вы хотите более аккуратную/чистую/понятную структуру директорий для ваших файлов.
5. Используйте одну из стандартных структур в качестве скелета
В официальной документации Go есть отличная статья, описывающая некоторые стандартные структуры проектов. Я использую одну из этих структур в качестве высокоуровневого «скелета» практически в каждом проекте Go, над которым я работаю в настоящее время, и рекомендую вам делать то же самое.
Небольшие проекты
Для небольших проектов рассмотрите возможность использования базовой структуры, где вы просто размещаете всё в корневой директории проекта, вот так:
1 2 3 4 5 |
├── main.go ├── foo.go ├── bar.go ├── go.mod └── README.md |
Пара реальных примеров проектов, использующих такую структуру: mkcert и flow.
Небольшие проекты с вспомогательными пакетами
Для проектов, где вам нужно вынести часть кода во вспомогательные пакеты, используйте структуру с вспомогательными пакетами. В этой схеме вспомогательные пакеты живут внутри директории internal
в корне проекта, а файлы вашего пакета main
и другие ресурсы проекта продолжают находиться в корневой директории.
1 2 3 4 5 6 7 |
├── internal │ └── foo │ └── foo.go ├── main.go ├── bar.go ├── go.mod └── README.md |
Более крупные проекты
Для более крупных проектов я обычно рекомендую использовать структуру серверного проекта, особенно если:
- Ваш проект будет иметь много нетекстовых ресурсов (например, файлы шаблонов, SQL-миграции, конфигурации инструментов и Makefile); или
- Ваш проект будет содержать более одного пакета
main
(например, пакетыmain
для веб-приложения и инструмента командной строки)
В этой структуре:
- Ваши исполняемые файлы пакета
main
находятся в поддиректориях под директориейcmd
- Остальные пакеты Go находятся в директории
internal
- Все другие ресурсы проекта остаются в корне директории проекта
Выглядит это так:
1 2 3 4 5 6 7 8 9 10 |
├── cmd │ └── foo │ ├── main.go │ └── bar.go ├── internal │ └── baz │ └── baz.go ├── go.mod ├── Makefile └── README.md |
Для более полного примера, вот структура директорий из недавнего проекта, над которым автор работал – включая пакеты main
для веб-сервера и CLI-приложения, вместе с различными не-Go ресурсами.
6. … А затем позвольте ей эволюционировать
Используйте одну из стандартных структур проекта в качестве своего высокоуровневого ‘скелета’, но помимо этого, я рекомендую позволить остальной структуре внутри этого скелета естественно развиваться по мере продвижения разработки.
Другими словами, не решайте заранее структуру директорий или какие файлы .go
у вас будут, а затем не втискивайте в них ваш Go-код. Вместо этого, позвольте коду, который вы пишете, направлять создание файлов и пакетов.
7. Если вы не уверены, начните с двух файлов
Если вы в каких-либо сомнениях, начните с базовой структуры и только с файлами go.mod
и main.go
в корне директории вашего проекта. Затем, по мере развития вашего проекта, добавляйте дополнительные файлы и пакеты по мере необходимости.
Начинать так совершенно нормально. Лично у меня около половины новых проектов, над которыми я работаю, начинаются только с этих двух файлов — и ничего больше.
8. Держите связанные вещи рядом
Это кажется довольно очевидным – особенно если вы опытный разработчик – но всё же стоит сказать. В общем случае, держите связанные вещи близко друг к другу – в одном файле .go
или в одном пакете. Вот несколько примеров:
- Константы, переменные, пользовательские типы и служебные функции (которые не используются повторно несколькими пакетами) должны быть объявлены рядом с кодом, который они поддерживают, в том же файле
.go
или пакете. - Имеет смысл группировать служебные функции, которые связаны друг с другом и используются в нескольких местах, в один многоразовый пакет.
- Если у вас есть пользовательский тип структуры, определяйте любые методы для него прямо под объявлением структуры в том же файле
.go
. - В веб-приложении или API определяйте все правила маршрутизации вместе в одной функции или файле
.go
.
Вероятно, будут моменты, когда в вашем коде имеет смысл нарушить правило ‘держите связанные вещи рядом’, и это нормально, но это хороший принцип, на который стоит опираться по умолчанию.
9. Большие файлы не обязательно плохи
Пока это не вызывает у вас практических проблем во время разработки или поддержки, размер файла в Go не имеет значения. Нормально иметь файлы .go
, содержащие как пару строк, так и тысячи. Ни одно из этих явлений автоматически не считается анти-паттерном в Go.
Чтобы дать вам представление о некоторых больших файлах: файл runtime/proc.go
из стандартной библиотеки Go содержит 6 548 строк кода. А файл /pkg/apis/core/validation/validation.go
из репозитория Kubernetes содержит 8 606 строк кода (соответствующий файл _test.go
также имеет более 26 000 строк).
Я не говорю, что ваши файлы .go
должны быть большими. Скорее, если в целом имеет смысл иметь большой файл… то это имеет смысл. Не чувствуйте себя виноватыми из-за этого и не думайте, что вам нужно разбивать его на более мелкие файлы, если на то нет веской причины.
10. Создавайте пакеты обдуманно
В том же духе, большие пакеты не обязательно плохи. Фактически, я бы сказал, что одна из более распространенных ошибок в Go — это разделение кода на слишком много маленьких пакетов.
Проблема наличия множества маленьких пакетов в том, что это может добавить сложности вашему приложению, особенно когда вам нужно делиться состоянием, конфигурацией или зависимостями через границы пакетов. Это также увеличивает вероятность столкновения с проблемами циклического импорта.
В качестве общего правила, создавайте дополнительные пакеты только тогда, когда у вас есть очевидная потребность или веская причина. Например:
- У вас есть код, который вы хотите использовать повторно. Размещение кода в автономном пакете облегчает это, потому что вы можете импортировать пакет и использовать его в разных файлах вашего проекта или даже скопировать и вставить директорию пакета прямо в другую кодовую базу.
- Вы хотите изолировать или обеспечить границу между кодом пакета и остальной частью вашего проекта. Например, вы можете использовать пакеты как архитектурный инструмент для создания легких несвязанных ‘слоев’ в коде вашего проекта или для изоляции части кодовой базы, чтобы другому человеку или команде было легче работать над ней отдельно.
- У вас есть код, который действует как ‘черный ящик’, и перемещение его в автономный пакет снижает когнитивную нагрузку и делает вашу кодовую базу в целом более понятной.
11. Обращайте внимание на предупреждающие знаки
Может быть трудно точно определить, когда структура вашего проекта работает эффективно… вместо этого, вероятно, легче заметить признаки того, что она не работает эффективно на практике. Вот на что стоит обратить внимание:
- Вы постоянно сталкиваетесь с проблемами циклических импортов.
- Трудно найти что-то в кодовой базе, особенно после времени отсутствия или для новых участников.
- Относительно небольшие изменения часто затрагивают несколько пакетов или файлов
.go
. - Поток управления слишком «прыгающий» и трудно отслеживать при отладке.
- Существует много дублирования, которое трудно вынести в отдельные компоненты (примечание: некоторое дублирование не всегда плохо).
- Вам сложно правильно управлять ошибками.
- Вы чувствуете, что «боретесь с языком», или вы прибегаете к использованию функций языка способом, который не является предполагаемым или идиоматическим.
- Кажется, что один файл или пакет делает слишком много, и что в нем нет четкого разделения обязанностей, и это оказывает негативное влияние на ясность вашего кода.
Если вы заметили эти предупреждающие знаки, возможно, стоит сделать шаг назад и подумать, поможет ли изменение структуры вашей кодовой базы и пакетов решить проблему.

Администрирую данный сайт с целью распространения как можно большего объема обучающего материала для языка программирования Go. В IT с 2008 года, с тех пор изучаю и применяю интересующие меня технологии. Проявляю огромный интерес к машинному обучению и анализу данных.
E-mail: vasile.buldumac@ati.utm.md
Образование
Технический Университет Молдовы (utm.md), Факультет Вычислительной Техники, Информатики и Микроэлектроники
- 2014 — 2018 Universitatea Tehnică a Moldovei, ИТ-Инженер. Тема дипломной работы «Автоматизация покупки и продажи криптовалюты используя технический анализ»
- 2018 — 2020 Universitatea Tehnică a Moldovei, Магистр, Магистерская диссертация «Идентификация человека в киберпространстве по фотографии лица»