Модули Go: go.mod и go.sum
Собрал программу, которая одновременно импортирует example.com/lib и example.com/lib/v2, и она запустилась: печатает «я версия 1» и «я версия 2». Две мажорные версии одной библиотеки живут в одном бинарнике - это прямое следствие того, как в Go устроены версии. Спрашивают, что лежит в go.mod и go.sum, что делает tidy и как подключить v2.
Стержень: go.mod описывает модуль и зависимости, go.sum фиксирует контрольные суммы, а мажорная версия меняет путь импорта.
// Формулировки: «зачем go.sum?», «что делает go mod tidy?», «как подключить v2?»
go.sum: что там на самом деле лежит
Подключил одну зависимость и заглянул в go.sum. Строк оказалось две на модуль: одна на содержимое архива, вторая с пометкой /go.mod - на его собственный файл go.mod. Вторая нужна, чтобы дерево зависимостей нельзя было подменить, даже не трогая код.
Смысл файла - воспроизводимость и защита от подмены. Кто-то переписал содержимое под тем же тегом - суммы не сойдутся, и сборка упадёт вместо того, чтобы молча собрать чужой код. Поэтому go.sum коммитят в репозиторий всегда.
// go.mod рядом хранит имя модуля, версию языка и список зависимостей. Пометка // indirect у зависимости означает, что напрямую её никто не импортирует - она пришла через чужой модуль.
- go.sum
- контрольные суммы: архив плюс сам go.mod
- // indirect
- зависимость пришла через чужой модуль
tidy убирает, get обновляет
Проверил разницу на живом проекте. Добавил через go get две зависимости, а импортирую в коде только одну. go mod tidy убрал лишнюю из require целиком и заодно снял пометку // indirect с оставшейся - она ведь импортируется напрямую. go.sum ужался обратно до двух строк.
Версии tidy не поднимает, это работа go get. Третья команда, go install, ставит исполняемый инструмент и go.mod проекта не трогает вовсе. Три команды, три разные задачи, и путать их дорого.
// Свежая грабля из того же прогона: go mod tidy подтянул последнюю версию библиотеки, а та потребовала go >= 1.25.0, и сборка встала. Директива go в go.mod - это требование к тулчейну, и чужой релиз может его поднять.
- go mod tidy
- синхронизация require с реальными импортами
- go install
- ставит инструмент, go.mod проекта не трогает
Мажорные версии и выбор чисел
Тот опыт с двумя версиями в одном бинарнике работает из-за семантического импорта: несовместимая версия обязана жить по другому пути. v2 подключается как example.com/lib/v2, и это спасает, когда две твои зависимости требуют разные мажорные - обе просто уживаются.
Правило проверяется тут же: я поменял импорт v2 на путь без суффикса, и сборка сразу сказала, что такого пакета не существует. Ноль и единица - исключение, у v0 и v1 суффикса нет.
// Версии выбирает алгоритм минимальных версий: берётся наименьшая, удовлетворяющая всем требованиям, а не самая свежая. Отсюда предсказуемость - обновление приходит, только когда его запросили явно.
- семантический импорт
- мажорная версия отражена прямо в пути
- минимальные версии
- берётся наименьшая подходящая, а не свежая
Как отвечать: «Зачем нужен go.sum и что делает go mod tidy?»
go.mod описывает сам модуль и его зависимости с версиями, а go.sum хранит контрольные суммы. Причём на каждый модуль там две строки: одна на архив с кодом, вторая на его собственный go.mod - чтобы нельзя было подменить и дерево зависимостей тоже. Нужен он для воспроизводимости и защиты: подменили содержимое под тем же тегом, суммы не сошлись, сборка упала вместо того, чтобы молча собрать чужой код. Поэтому go.sum всегда коммитят. go mod tidy сверяет объявленные зависимости с реальными импортами: недостающее добавляет, лишнее убирает и приводит в порядок go.sum. Я это проверял - добавил две зависимости, импортировал одну, tidy вычистил вторую и заодно снял пометку indirect с оставшейся. Версии он не поднимает, это делает go get, а go install ставит исполняемый инструмент и go.mod проекта не трогает. Отдельно про версионирование: действует семантический импорт, мажорная версия отражается в пути, v2 подключается как example.com/lib/v2. Благодаря этому две мажорные версии спокойно живут в одной программе, я собирал такую специально. А выбор версий идёт по алгоритму минимальных: берётся наименьшая, удовлетворяющая требованиям, поэтому сборка предсказуема и не меняется от чужого релиза.
Сильный ответ: назначение go.sum объяснено через воспроизводимость и защиту, причём с деталью про две строки на модуль, границы tidy, get и install разведены явно, и добавлены две вещи, которые редко знают - семантический импорт мажорных версий и алгоритм минимальных версий.
На чём валят
- −Не коммитят go.sum и теряют защиту от подмены зависимости под тем же тегом.
- −Ждут от go mod tidy обновления версий. Это работа go get.
- −Подключают v2 по старому пути без суффикса и получают «пакета не существует».
- −Оставляют replace в публикуемой библиотеке: у потребителей он не работает вовсе.
- −Думают, что Go тянет самые свежие версии. Берётся минимальная подходящая.
Проверьте себя
Пять вопросов из банка по этой подтеме. Всего их 7, остальные разбираются в тренажёре.
- Зачем нужен файл go.sum?A)Хранит закэшированные скомпилированные бинарники всех зависимостей для офлайн-сборкиB)Держит криптохеши модулей — проверка целостности и защита от подменыC)Перечисляет доступные новые версии зависимостей для обновления через go get -uD)Дублирует содержимое go.mod ради обратной совместимости со старыми версиями Go
показать ответ и разбор
+B)Держит криптохеши модулей — проверка целостности и защита от подмены// разбор: go.sum хранит криптографические хеши содержимого каждой версии зависимости (и её go.mod). При сборке Go сверяет скачанное с этими хешами — гарантия, что модуль не подменили и он бит-в-бит тот же на всех машинах и в CI. Это не кэш бинарников (тот в модульном кэше) и не список версий. Коммитят оба: go.mod и go.sum.
- За что отвечают go vet и gofmt в тулчейне Go?A)gofmt форматирует код по канону, go vet ищет подозрительные конструкцииB)Оба форматируют код, просто gofmt новее и со временем заменяет собой go vetC)go vet собирает бинарник приложения, а gofmt отвечает за запуск тестов пакетаD)Это сторонние линтеры, которые нужно ставить отдельно через go get из внешнего репозитория
показать ответ и разбор
+A)gofmt форматирует код по канону, go vet ищет подозрительные конструкции// разбор: gofmt (и go fmt) приводит код к единому каноническому стилю — в Go форматирование не обсуждают, оно одно на всех. go vet — встроенный статический анализатор подозрительных, но компилируемых конструкций: неверные строки формата Printf, копирование мьютекса по значению, потерянный cancel контекста и т.п. Оба встроены в тулчейн; глубже — сторонний staticcheck.
- Что делает директива replace в файле go.mod?A)Подменяет источник модуля — например, на локальный каталог или форкB)Заменяет версию зависимости во всех модулях, которые её используютC)Удаляет неиспользуемые зависимости при следующей сборкеD)Переименовывает пакет, чтобы избежать конфликта имён при импорте
показать ответ и разбор
+A)Подменяет источник модуля — например, на локальный каталог или форк// разбор: Типичные поводы — отладить правку в библиотеке до её публикации, указав локальный путь, или временно сесть на форк с исправлением. Ключевая тонкость: replace действует только в главном модуле сборки и игнорируется, когда ваш модуль подключают как зависимость, — поэтому оставлять его в опубликованной библиотеке нельзя, у пользователей он просто не сработает.
- Что произойдёт с импортом, если библиотека выпустит мажорную версию v2?A)Ничего: go get подтянет новую версию по прежнему пути импортаB)Путь импорта изменится — в конец добавится суффикс /v2C)Старая версия перестанет собираться и потребует обновленияD)Версия указывается в go.mod, а путь импорта остаётся неизменным
показать ответ и разбор
+B)Путь импорта изменится — в конец добавится суффикс /v2// разбор: Правило семантического импорта: несовместимая версия — другой путь. Поэтому v2 подключается как example.com/lib/v2, и в одной программе спокойно живут обе версии сразу — это спасает, когда две зависимости тянут разные мажорные. Ноль и единица — исключение: у v0 и v1 суффикса нет.
- Что делает команда go mod tidy?A)Обновляет все зависимости до последних доступных версийB)Скачивает зависимости в каталог vendor внутри проектаC)Приводит go.mod и go.sum в соответствие с реальными импортами кодаD)Проверяет зависимости на известные уязвимости по базе данных
показать ответ и разбор
+C)Приводит go.mod и go.sum в соответствие с реальными импортами кода// разбор: Команда сверяет объявленные зависимости с тем, что действительно импортируется: недостающее добавляет, лишнее убирает, попутно приводя в порядок go.sum. Обновлением версий она не занимается — это go get. Проверка уязвимостей — отдельный инструмент govulncheck, а каталог vendor наполняет go mod vendor.
дальше
Теорию прочитали. Навык ставится повторением
В Сеньорчике эта подтема идёт в ежедневных сессиях: движок возвращает её, пока ответы не станут уверенными, и ведёт прогресс отдельно по каждой подтеме. Теория внутри тоже бесплатна, лимит только на количество вопросов в день.