go mod: модули, версии, replace и vendor

Модуль — набор пакетов с go.mod; его module path одновременно является импортным префиксом. Команда go строит минимальную версию (MVS): для каждого модуля выбирается наибольшая явно требуемая версия, а не «последняя вообще».

Зачем это на интервью

Проверяется умение безопасно обновлять зависимости, объяснить совместимость и расследовать различие локальной и CI-сборок.

Минимум для E4

  • Создать модуль: go mod init example.com/acme/service.
  • Понимать назначение go.mod (требования) и go.sum (проверяемые хеши модулей).
  • После изменения импортов запускать go mod tidy, затем проверять go test ./....
  • Знать, что replace локален для главного модуля и не передаётся потребителям библиотеки.
go mod init example.com/acme/service
go get example.com/lib@v1.4.2
go mod tidy
go list -m all
go mod graph
go mod why -m example.com/lib
go mod verify

Углубление для E5/Senior

Minimal Version Selection и обновления

MVS даёт повторяемый выбор для зафиксированного go.mod, но транзитивные зависимости всё ещё могут влиять на итоговый граф. Перед обновлением смотрят changelog, лицензии, advisory, go mod graph и integration-тесты. go get module@version меняет требования; go mod tidy удаляет неиспользуемые и добавляет требуемые записи по пакетам, тестам и build tags главного модуля.

Semantic Import Versioning

Для v0 и v1 путь обычно example.com/lib. Начиная с v2 major-версия входит в import path: example.com/lib/v2. Это позволяет v1 и v2 сосуществовать, но требует миграции импортов. Тег v2.0.0 без суффикса /v2 в module-директиве не делает модуль корректной v2-библиотекой.

import "example.com/lib/v2"

replace, exclude и vendor

replace example.com/lib => ../lib полезен для локальной разработки или временного fork; его нельзя считать механизмом доставки зависимости пользователям. exclude запрещает конкретную версию и применяется осторожно: лучше понять, почему она вошла в граф. go mod vendor копирует нужные исходники в vendor/; go build -mod=vendor ./... использует их. Для Go 1.14+ при согласованном vendor-каталоге команда может выбрать его автоматически, поэтому после обновления запускают go mod vendor и коммитят изменения намеренно.

Ключевые понятия

ТерминЗначение
module pathканонический путь модуля и префикс импорта
MVSвыбор максимальной требуемой версии каждого модуля
go.sumchecksums скачанных модульных содержимых, не lockfile
SIVmajor v2+ в import path
vendorснимок исходников зависимостей для сборки

Типовые вопросы

  1. Почему go.sum нужно коммитить?
    • Он фиксирует проверяемые хеши и позволяет обнаружить подмену/неожиданное содержимое; это не секрет.
  2. Как узнать, кто принёс зависимость?
    • go mod why -m module показывает путь использования, go mod graph — рёбра графа.
  3. Что делает go mod tidy?
    • Синхронизирует require/sum с пакетами главного модуля; его вывод надо ревьюить как изменение зависимостей.
  4. Почему v2 требует /v2?
    • Это часть Semantic Import Versioning, различающая несовместимые API на уровне импортов.
  5. Когда оправдан vendor?
    • Когда политика или изолированная сборка требует исходников зависимостей в репозитории; он добавляет объём и требует синхронизации.

Практика

  • Создайте модуль, добавьте прямую и транзитивную зависимость. Готово: объяснены строки go.mod, выводы why и graph, verify проходит.
  • Смоделируйте миграцию v1→v2. Готово: module path, тег и все импорты согласованы; обе major-версии объяснены.
  • Сделайте vendor-снимок. Готово: go mod vendor, go test -mod=vendor ./... и обычный тест проходят после чистого checkout.

Частые ошибки и ловушки

  • Удалять go.sum, чтобы «починить» зависимость, не понимая причины изменения.
  • Коммитить replace ../local в библиотеку как будто он влияет на потребителей.
  • Помечать breaking API тегом v1.x или забывать /v2.
  • Обновлять десятки модулей без тестов и review графа.

Связанные темы

Инструменты Go · Delivery · Тестирование

Источники