Create your own
Lesson illustration

Создание Go-модуля и управление зависимостями с помощью основных команд Go

Здравствуйте! Начинаем модуль быстрого восстановления Go. В этой первой части вы соберёте минимальный, но реалистичный рабочий цикл Go-проекта: модуль, исходный код, форматирование, запуск, сборка, тест и управление зависимостями. Это основа, на которой дальше будут строиться backend-сервис и портфолио-проект.

К концу урока у вас будет репозиторий-заготовка task-service, который можно запускать и проверять одной последовательностью команд.


Модуль, пакет и репозиторий: не смешивать понятия

В Go репозиторий хранит исходный код и историю Git. Внутри него обычно находится один модуль. Модуль объединяет один или несколько пакетов и задаёт границу версионирования и внешних зависимостей.

Схема показывает, что один Go-модуль содержит один или несколько пакетов, а в его корне располагаются файлы `go.mod` и, при наличии внешних зависимостей, `go.sum`.

Для будущего backend-проекта типичная картина будет такой:

task-service/                 # репозиторий и корень модуля
├── go.mod                    # имя модуля, версия Go, зависимости
├── go.sum                    # контрольные суммы скачанных модулей
├── cmd/
│   └── api/
│       └── main.go           # точка входа приложения
├── internal/                 # прикладной код, недоступный внешним модулям
└── ...

Сегодня начнём проще: один пакет main в корне. Позже вы разнесёте приложение по пакетам, но модуль останется его общей границей.

go.mod создаётся командой go mod init. В нём находятся:

  • путь модуля;
  • целевая версия Go;
  • прямые и косвенные зависимости.

go.sum содержит криптографические контрольные суммы версий модулей, которые Go скачивал для разрешения зависимостей. Он помогает убедиться, что полученный код соответствует опубликованной версии.

Важно: go.sum может отсутствовать в маленьком проекте без внешних зависимостей. Когда они появятся, Go создаст или дополнит этот файл. Оба файла, если они есть, нужно коммитить в Git. Их обычно не редактируют вручную.

Go Modules Explained | Mastering Dependency Management in Go

Посмотрите фрагменты видео «Go Modules Explained» от Code & Learn. Они дают короткую визуальную модель связи между репозиторием, модулем, пакетами, go.mod и go.sum.

Посмотрите иерархию проекта, чтобы закрепить различие между репозиторием, модулем и пакетом. Затем посмотрите создание модуля: обратите внимание, почему путь модуля похож на будущий путь репозитория. В завершение посмотрите роль go sum и запомните практическое правило: go.mod и go.sum должны попадать в систему контроля версий.


Создаём заготовку будущего сервиса

Откройте терминал и сначала убедитесь, что Go доступен:

go version

Создайте каталог проекта и инициализируйте модуль. Вместо YOUR_GITHUB_LOGIN подставьте свой реальный GitHub-логин:

mkdir task-service
cd task-service

go mod init github.com/YOUR_GITHUB_LOGIN/task-service

После этого в текущем каталоге появится go.mod. Его первая строка будет примерно такой:

module github.com/YOUR_GITHUB_LOGIN/task-service

Путь модуля — не просто подпись. Он становится префиксом импортов ваших внутренних пакетов. Например, когда позднее появится пакет internal/task, его импорт из кода внутри проекта будет начинаться с:

github.com/YOUR_GITHUB_LOGIN/task-service/internal/task

Поэтому для портфолио-проекта сразу выбирайте путь, совпадающий с предполагаемым адресом репозитория. Если позже вы создадите репозиторий по адресу github.com/ivanov/task-service, а в go.mod останется другой путь, проект может локально работать, но будет создавать проблемы при импортах, публикации и установке.

Создайте main.go:

package main

import "fmt"

func main() {
	fmt.Println("task-service: started")
}

Теперь выполните:

go run .

Точка в команде означает: «собери и запусти пакет из текущего каталога». Поскольку это пакет main с функцией main, Go знает, что перед ним исполняемая программа.

go run удобен в разработке:

  • компилирует код;
  • запускает результат;
  • использует временный кэш, не оставляя бинарный файл в рабочем каталоге.

Проверьте, что терминал действительно находится в корне модуля:

go env GOMOD

Команда должна вывести путь к вашему go.mod. Если вместо этого выводится /dev/null, вы либо находитесь вне Go-модуля, либо инициализировали модуль не там, где ожидали.


Пять команд ежедневного цикла разработки

В Go значительная часть базовой инженерной дисциплины встроена в стандартный инструмент go. Для начинающего backend-разработчика полезно довести следующий цикл до автоматизма.

КомандаНазначениеКогда применять
go fmt ./...Форматирует Go-код во всех пакетах модуляПеред коммитом и после заметных изменений
go run .Собирает и запускает текущую программуВо время локальной разработки
go build .Компилирует программу в бинарный файлЧтобы проверить самостоятельную сборку
go test ./...Компилирует и запускает тесты во всех пакетахПосле каждого логически завершённого изменения
go mod tidyСинхронизирует зависимости с импортами кодаПосле добавления или удаления библиотек

Форматирование: go fmt

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

go fmt ./...

Шаблон ./... означает «текущий каталог и все вложенные пакеты». Сейчас пакет один, но привычка запускать команду для всего модуля избавит от пропущенного неотформатированного файла, когда структура станет больше.

go fmt изменяет файлы на диске. После неё полезно посмотреть изменения через Git:

git diff

На CI позже обычно проверяют, что форматирование не создаёт изменений. Локально же правило простое: сначала форматируйте, затем коммитьте.

Запуск и сборка: go run против go build

Выполните:

go build .

В корне проекта появится исполняемый файл с именем каталога: task-service на Linux/macOS или task-service.exe на Windows. В отличие от go run, команда go build не запускает программу. Она отвечает только на вопрос: можно ли собрать этот код в отдельный исполняемый файл?

Запустите результат:

./task-service

На Windows:

.\task-service.exe

В реальном проекте не стоит оставлять локальные бинарные файлы в Git. Добавьте в .gitignore:

/task-service
/task-service.exe
/bin/

Позже вы будете собирать приложение в bin/ или в Docker-образе, но принцип не изменится: исходный код и собранный артефакт — разные вещи.

Тесты: go test

Создайте рядом с main.go файл main_test.go:

package main

import "testing"

func TestSmoke(t *testing.T) {
}

Содержательный тест вы напишете в отдельном модуле тестирования. Сейчас нужна минимальная проверка того, как инструмент распознаёт тестовый файл: имя оканчивается на _test.go, а тестовая функция начинается с Test.

Запустите проверку всего модуля:

go test ./...

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

Полезное различие:

  • go test проверяет пакет в текущем каталоге;
  • go test ./... проверяет текущий пакет и вложенные пакеты;
  • go build . собирает текущую программу;
  • go run . собирает и сразу запускает текущую программу.

Для backend-проекта почти всегда используйте go test ./..., чтобы случайно не проверить только один пакет.


Как Go управляет внешними зависимостями

Стандартная библиотека Go — например, fmt, net/http, context, testing — не требует отдельной загрузки и не появляется в go.mod.

Внешняя библиотека находится в другом модуле. Вы импортируете пакет, но Go фиксирует версию модуля, в котором этот пакет расположен. Это различие важно на собеседованиях и в повседневной работе:

  • пакет — единица организации кода и импорта;
  • модуль — единица зависимостей и версий.

Официальная документация Go хорошо описывает жизненный цикл зависимости.

Managing dependencies

Прочитайте выдержки из официального руководства Go «Managing dependencies». Они задают корректную практику: как выбирать путь модуля, что именно меняют go get и go mod tidy, и почему файлы модуля нужно хранить вместе с исходным кодом.

В разделе «Enabling dependency tracking in your code» прочитайте описание файлов модуля. Сопоставьте это со своим только что созданным go.mod. Затем в разделе «Adding a dependency» изучите действия go get: команда меняет требования в go.mod, скачивает код в локальный кэш и проверяет его целостность. Наконец, в разделе «Synchronizing your code’s dependencies» прочитайте от абзаца, начинающегося процедуру tidy. Зафиксируйте главное: go mod tidy добавляет недостающие зависимости и удаляет более не нужные.

Практика: импорт, затем go mod tidy

Для демонстрации подключим небольшую внешнюю библиотеку rsc.io/quote. Временно замените содержимое main.go:

package main

import (
	"fmt"

	"rsc.io/quote"
)

func main() {
	fmt.Println(quote.Go())
}

На этом этапе импорт уже есть, но требование к модулю ещё не зафиксировано. Выполните:

go mod tidy

Go проанализирует импорты во всём модуле, найдёт модуль rsc.io/quote, скачает его и обновит файлы зависимостей. После этого выполните:

go run .
go test ./...

Посмотрите изменения:

git diff -- go.mod go.sum

В go.mod появится require с модулем и его версией. Возможно, вы также увидите косвенные зависимости с комментарием // indirect: они нужны библиотеке, которую вы подключили, но не импортируются вашим кодом напрямую.

go.sum не следует воспринимать как простой перечень «установленных пакетов». Он хранит контрольные суммы конкретных версий модулей, нужных для воспроизводимой и проверяемой загрузки. Грубая аналогия с lock-файлами из других экосистем допустима, но важнее помнить практическое правило: не удаляйте и не переписывайте go.sum вручную; пусть им управляют Go-инструменты.

Когда нужен go get

Обычный рабочий путь для новой библиотеки:

  1. добавить импорт в код;
  2. выполнить go mod tidy;
  3. запустить тесты.

go get особенно полезен, когда вы хотите явно выбрать или изменить версию зависимости:

go get rsc.io/quote@v1.5.2

Или когда нужно обновиться до доступной последней версии:

go get rsc.io/quote@latest

После изменения версии не ограничивайтесь командой go get. Проверьте изменения и совместимость:

go mod tidy
go test ./...

Не обновляйте все зависимости «на всякий случай» перед дедлайном. Обновление версии — это изменение поведения поставляемого кода; оно требует тестов и осмысленного просмотра diff в go.mod и go.sum.


Завершите мини-проект чистым состоянием

Библиотека rsc.io/quote была нужна только для демонстрации. В портфолио-проекте не должно быть зависимостей, которые не решают реальную задачу.

Верните main.go к варианту без внешнего импорта:

package main

import "fmt"

func main() {
	fmt.Println("task-service: started")
}

Теперь выполните полный локальный цикл:

go fmt ./...
go mod tidy
go test ./...
go build .
go run .

После удаления внешнего импорта go mod tidy должен убрать ненужное требование из go.mod. Если других внешних модулей нет, файл go.sum может исчезнуть — это нормальный результат.

Перед первым коммитом проверьте статус:

git status

В репозитории должны оказаться:

.gitignore
go.mod
main.go
main_test.go

А go.sum добавляйте, если он существует после go mod tidy.


Типичные ошибки и быстрый диагноз

go: cannot find main module
Команда запущена вне каталога с go.mod. Перейдите в корень проекта или инициализируйте модуль там, где он должен находиться.

Модуль назван просто task-service
Для временного локального эксперимента это допустимо, но для GitHub-репозитория и портфолио лучше сразу использовать полный путь: github.com/логин/task-service. Короткое имя может конфликтовать с импортами и не отражает реальное расположение кода.

Код импортирует библиотеку, но go.mod не обновлён
Запустите:

go mod tidy

В go.mod осталась библиотека, которую больше не используете
Снова выполните go mod tidy. Он синхронизирует объявленные зависимости с реальными импортами.

Компилятор сообщает об unused import
Это ошибка исходного кода, а не проблема модулей. Уберите неиспользуемый импорт или начните реально использовать пакет. go mod tidy не превращает некорректный Go-код в корректный.

В коммите нет go.sum
Если файл создан, его нужно коммитить. Иначе у коллеги или CI может отличаться проверяемый набор скачанных модулей.


Сегодня вы восстановили базовую модель Go-проекта: модуль задаёт границы зависимостей и версионирования, пакеты организуют код, а go.mod и go.sum фиксируют информацию, необходимую инструментам Go. Вы также собрали ежедневный цикл:

go fmt ./...
go mod tidy
go test ./...
go build .
go run .

В следующем уроке перейдём к переменным, базовым типам, условиям и циклам — и превратим текущую заготовку в небольшую программу с реальной логикой.

Can't find a good explanation? Sign up and we'll make it for you

Sign up