1. Навіщо взагалі дві команди: go get і go mod tidy
Коли проєкт ще маленький, здається, що залежності «якось самі з’являються»: імпортували пакет — і все зібралося. Але щойно ви починаєте використовувати зовнішні бібліотеки, тобто ті, що не входять до стандартної бібліотеки, у проєкту з’являється ще одне важливе завдання: зафіксувати, які саме версії зовнішнього коду ви використовуєте, щоб збірка була відтворюваною. І ось тут Go поводиться дисципліновано: він не просто завантажує щось з інтернету, а акуратно записує ваш вибір у файли проєкту.
У цій лекції розберемо дві команди, які найчастіше викликають або повагу, або легку паніку — зазвичай одночасно: go get і go mod tidy. Одразу домовимося: ми не заглиблюємося в replace, vendor та інші «швейцарські ножі» модульної системи. Наша мета — навчитися керувати версіями залежностей і підтримувати порядок у go.mod/go.sum.
Модель у голові: що взагалі вважається «залежністю»
Якщо говорити просто, залежність з’являється тоді, коли ваш код імпортує пакет, який не входить до стандартної бібліотеки. Стандартна бібліотека — це fmt, strings, time, net/http і друзі. Усе, що виглядає як github.com/... або golang.org/x/..., — уже зовнішнє.
Далі починається важлива частина. Go думає не «пакетами», а модулями. Один модуль може містити багато пакетів. Ви імпортуєте пакет, але фіксуєте в go.mod модуль і його версію. Тому іноді ви імпортуєте golang.org/x/net/html, а в go.mod фіксується модуль golang.org/x/net цілком.
І ще один ключовий момент: залежності бувають прямі (direct) і транзитивні (transitive). Прямі — це те, що імпортуєте ви. Транзитивні — те, що імпортують ваші залежності. Це як знайомство через знайомих: ви запросили одну людину, а вона прийшла з друзями — інколи корисними, інколи галасливими. Але таке вже життя.
2. Що робить go get: «обрати версію і записати вибір»
go get — це команда про вибір версій модулів. Не про «скачати бібліотеку», не про «встановити все на комп’ютер», а саме про те, щоб сказати: «Проєкту, ось цю залежність бери в такій версії». Після цього Go оновлює go.mod і майже завжди go.sum.
Є корисна деталь: сучасні go-команди часто не намагаються виправити проблему автоматично, а натомість показують помилку й прямим текстом підказують команду, яку варто виконати. Наприклад, під час збірки ви можете побачити повідомлення на кшталт: «no required module provides package ...; to add it: go get ...». Це нормальний сценарій: Go не мовчки чаклує, а каже, що саме не так і який наступний крок очікується.
Базовий сценарій: «додати залежність за імпортом»
Уявімо, що в нашому навчальному застосунку todoapp ми хочемо додати невелику фічу: нормалізувати заголовки задач не лише через strings.TrimSpace, а й, скажімо, підготуватися до складнішої обробки тексту. Для прикладу візьмемо зовнішній пакет golang.org/x/net/html — він часто трапляється в реальному житті й добре підходить як «навчальна» залежність.
Припустімо, ви додали імпорт у код, а потім запускаєте збірку:
go build ./...
Якщо модуль не додано, Go може видати помилку й підказку «to add it: go get ...». Такий стиль повідомлень характерний для режиму з підтримкою модулів і спеціально так задуманий, щоб ви не гадали, яку команду вводити.
Тоді ви виконуєте:
go get golang.org/x/net/html
Тепер Go робить кілька важливих кроків: обирає версію модуля golang.org/x/net, записує її в go.mod і додає контрольні суми в go.sum.
Керування версіями: @version, @latest, відкат
Дуже корисно сприймати go get як «перемикач версій».
Ви можете явно вказати версію:
go get golang.org/x/net@v0.20.0
Ви можете попросити найсвіжішу версію, яка підходить відповідно до правил вибору версій:
go get golang.org/x/net@latest
І так, ви можете відкотитися на старішу:
go get golang.org/x/net@v0.19.0
Важливо звикнути до думки: go get — це не «встанови бібліотеку», а «зміни вимоги проєкту до версій». Тому після нього майже завжди є зміни в go.mod/go.sum. І це нормально.
4. Що робить go mod tidy: «навести лад і синхронізувати факти»
Якщо go get — це команда про «вибір версій», то go mod tidy — це команда про «давайте приведемо декларацію залежностей у відповідність до реальності».
На практиці є дві сторони: ваш код і ваші тести. go mod tidy дивиться на імпорти у вихідних файлах і у файлах _test.go та робить дві основні речі: додає те, чого бракує для збірки й тестів, і видаляє те, що більше не використовується. При цьому оновлюються і go.mod, і go.sum.
Уявіть, що go.mod — це список покупок, а проєкт — це ваша кухня. go mod tidy — це коли ви не просто докупили молока (go get), а ще й викинули зі списку покупок «манго», яке ви додали в пориві ентузіазму два тижні тому й так і не купили.
5. Корисні нюанси
Чому в go.mod з’являється // indirect
Новачки часто сприймають // indirect як щось на кшталт «це залежність другого сорту». Насправді це просто підказка: залежність не імпортується вашим кодом напряму, але потрібна для збірки через інші модулі.
go mod tidy якраз любить розставляти ці позначки й прибирати їх, коли ситуація змінюється. Пам’ятайте: не потрібно вручну воювати з // indirect, видаляти їх «для краси» або дописувати «щоб було правильно». Правильність визначає інструмент, а ваше завдання — розуміти сенс і тримати проєкт у стані, що успішно збирається.
Міні-таблиця: хто за що відповідає
Після теорії корисно чітко розкласти, хто за що відповідає в різних ситуаціях. Таблиця невелика, але часто економить 20 хвилин роздратування.
| Ситуація в проєкті | Що ви хочете зробити | Команда, яка зазвичай доречна | Що зміниться |
|---|---|---|---|
| Додали новий імпорт зовнішнього пакета, збірка не проходить | Зафіксувати модуль і версію | |
|
| Хочете оновити залежність до конкретної версії | Обрати версію | |
|
| Видалили імпорт/код, але залежності залишилися зайвим хвостом | Прибрати зайве | |
|
| Підозрюєте, що go.mod і код «роз’їхалися» | Синхронізувати факти | |
|
Фраза, яку варто запам’ятати: go get — про версії, go mod tidy — про порядок.
6. Приклад на todoapp
Уявімо структуру проєкту (спрощено):
todoapp/
go.mod
go.sum
cmd/todoapp/main.go
internal/todo/normalize.go
Додамо функцію нормалізації без зовнішніх залежностей
Почнемо з простого й знайомого. Файл internal/todo/normalize.go:
package todo
import "strings"
// NormalizeTitle приводить заголовок до охайного вигляду.
func NormalizeTitle(s string) string {
return strings.TrimSpace(s)
}
А тепер використаємо це в cmd/todoapp/main.go:
package main
import (
"fmt"
"example.com/todoapp/internal/todo"
)
func main() {
fmt.Println(todo.NormalizeTitle(" купити молоко ")) // купити молоко
}
Поки все тримається на стандартній бібліотеці, залежності не додаються.
Додамо зовнішній імпорт для демонстрації механіки
Тепер додамо штучну, але правдоподібну причину використати зовнішній пакет. Наприклад, ми хочемо в майбутньому парсити HTML — нехай це буде лише навчальний приклад. Змінимо normalize.go, додавши посилання на тип із golang.org/x/net/html:
package todo
import (
"strings"
"golang.org/x/net/html"
)
// NormalizeTitle приводить заголовок до охайного вигляду.
func NormalizeTitle(s string) string {
_ = html.Node{} // просто щоб імпорт був «живим»
return strings.TrimSpace(s)
}
Найімовірніше, після цього go build ./... почне скаржитися, що потрібний модуль не додано, і запропонує виконати go get. Такий формат підказки — очікувана поведінка інструментів Go.
Виправлення — виконати:
go get golang.org/x/net/html
Після цього відкрийте go.mod: там з’явиться require для golang.org/x/net .... Також з’являться або оновляться записи в go.sum.
Прибираємо зовнішній імпорт і чистимо хвости
Припустімо, ви передумали, видалили html.Node{} і сам імпорт. Після цього проєкт знову збирається, але залежність може залишитися в go.mod, бо ви колись її додали.
Ось тут настає час go mod tidy:
go mod tidy
Ця команда синхронізує залежності з фактичними імпортами. Вона прибере з go.mod те, що більше не потрібно, і підчистить go.sum. Це очікувано: обидві команди керують цими файлами за призначенням.
7. Як читати go.mod і не боятися go.sum
Коли ви працюєте в команді або просто цінуєте охайність, ви бачитимете зміни в go.mod і go.sum. Це нормальна частина життя Go-проєкту.
go.mod — зрозумілий і відносно людяний. Там можна побачити module, go, список require. Його можна читати, а іноді й редагувати вручну, але обережно й з розумінням наслідків.
go.sum — це не «другий go.mod». Він зберігає контрольні суми модулів і слугує для перевірки цілісності та відтворюваності. До нього краще ставитися як до продукту роботи інструментів. На практиці майже завжди правильний шлях такий: ви змінюєте код і версії командами, а go.sum оновлюється автоматично. Ручне редагування go.sum зазвичай закінчується тим, що ви або ламаєте збірку, або за хвилину все одно знову запускаєте go mod tidy, і інструмент повертає все назад.
8. Схема життєвого циклу залежності
Іноді корисно побачити процес як «конвеєр», а не як набір випадкових команд.
flowchart TD
A[Додали імпорт зовнішнього пакета в код] --> B[go build ./... або go test ./...]
B -->|помилка: немає модуля| C[go get
]
C --> D[оновилися go.mod/go.sum]
D --> E[код знову збирається]
E --> F[видалили імпорт/код]
F --> G[go mod tidy]
G --> H[go.mod/go.sum синхронізовані]
Тут важливий сам ритм: go get зазвичай з’являється, коли ви додаєте залежності або змінюєте версії, а go mod tidy — коли ви хочете, щоб файли залежностей знову відповідали поточному стану коду.
9. Типові помилки
Помилка №1: сприймати go get як «просто скачати бібліотеку один раз».
Таке мислення походить зі світу, де ви вручну встановлюєте пакети в систему. У Go це працює інакше: go get фіксує версії залежностей саме для поточного модуля й змінює go.mod/go.sum. Тому «я просто хотів завантажити» перетворюється на зміни в репозиторії — і це не баг, а сенс команди.
Помилка №2: запускати go mod tidy, не розуміючи, що він враховує ще й тести.
Іноді студент видаляє імпорт з основного коду, запускає go mod tidy, а залежність чомусь залишається. Часто причина в тому, що цей імпорт живе в _test.go. Це не «Go впирається», це ви забули, що тести — теж частина проєкту, і для їхньої збірки потрібні залежності.
Помилка №3: правити go.sum руками, щоб «зійшлося».
go.sum — не місце для ручної творчості. Якщо ви бачите, що він змінився, це зазвичай наслідок зміни залежностей або їхніх версій. Правильна дія — зрозуміти причину: що додали, оновили або видалили, а не «підкручувати файл», бо інструмент усе одно перевірить цілісність і може знову перегенерувати записи.
Помилка №4: плутати шлях імпорту пакета і шлях модуля під час go get.
Іноді пишуть go get з одним шляхом, а імпортують інший, і з’являється відчуття «магії». Правило просте: ви можете вказувати go get і на конкретний пакет (.../html), і на корінь модуля (.../net). Go сам розбереться, який модуль потрібен. Але важливо, щоб імпорт у коді був коректним і відповідав реальному розташуванню пакета.
Помилка №5: лякатися змін у go.mod після go get або go mod tidy.
Інструменти Go очікувано оновлюють файли залежностей, бо їхнє головне завдання — керувати залежностями проєкту, і go get, і go mod tidy саме для цього й призначені. Якщо ви щоразу намагаєтеся «відкотити назад, бо страшно», ви позбавляєте проєкт головної переваги: відтворюваності.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ