Перейти до змісту

Вибір та управління залежностями

Усе, про що йдеться далі, потрапляє в go.mod. Решта цієї книги — це мова та її стандартна бібліотека, які залишаються незмінними, що б ви не будували; цей розділ — це вибір одного конкретного стеку, а вибір потребує підтримки.

go get github.com/google/uuid
go mod tidy

Оцініть модуль перед тим, як додати його

Залежність — це постійне зобов'язання покладатися на чужу думку. Варте двох хвилин:

  • Чи його підтримують? Свіжі коміти, відповіді на issue, релізи, що не всі датовані одними вихідними три роки тому.
  • Яке його власне дерево залежностей? go mod graph після додавання. Бібліотека, що тягне за собою сорок транзитивних модулів, приносить сорок додаткових речей, які можуть зламатися.
  • Чи це v1? Модуль v0 не дає жодних обіцянок щодо сумісності. Це прийнятно для інструменту, ризикованіше для чогось на шляху обробки запиту.
  • Чи могли б ви написати це самі? Двадцятирядковий хелпер не вартий окремого модуля. Стандартна бібліотека Go достатньо широка, тож у багатьох популярних пакетах з інших екосистем тут немає аналогів, бо вони просто не потрібні.
  • Скільки коштує його прибрати? Бібліотека за вашим власним інтерфейсом замінна. Та, чиї типи з'являються в кожній сигнатурі — ні.

Стандартна бібліотека — типовий вибір. Виходьте за її межі, коли проблема справді велика — вебфреймворк, драйвер бази даних, ORM — а не щоб зекономити десять рядків.

go.mod і go.sum

go.mod записує шлях модуля, версію Go і прямі вимоги. go.sum записує криптографічні хеші кожного модуля в графі — прямого й непрямого.

Комітьте обидва. go.sum — це те, що робить збірки відтворюваними й виявляє підмінену залежність: невідповідність хешу провалює збірку замість того, щоб мовчки запустити інший код.

// indirect позначає вимогу, яку ваш код не імпортує напряму — вона тут, бо потрібна залежності.

go mod tidy      # додати використане, прибрати невикористане
go mod graph     # весь граф залежностей
go mod why <pkg> # чому це взагалі тут

go mod why — те, до чого варто вдатися, коли tidy додає щось неочікуване. Запускайте tidy перед кожним комітом, що торкається імпортів; CI має перевіряти, що це не дає diff.

Версії й оновлення

Модулі використовують семантичне версіювання, і інструментарій сприймає це буквально.

go get github.com/x/y@v1.4.2     # точна версія
go get github.com/x/y@latest     # найновіший реліз
go get -u ./...                  # оновити мінорні й патч-версії
go get github.com/x/y@none       # видалити

Go використовує вибір мінімальної версії (minimal version selection): збірка обирає найнижчу версію, що задовольняє кожну вимогу, а не найвищу з доступних. Тому збірки відтворювані без lock-файлу — оновлення відбуваються, коли ви просите, а не за чиїмось чужим розкладом.

go get -u піднімає мінорні й патч-версії, але ніколи мажорні, бо мажорна версія — це інший шлях модуля.

Правило /vN

Починаючи з v2, мажорна версія стає частиною шляху імпорту:

import "github.com/pelletier/go-toml/v2"
import "github.com/jackc/pgx/v5"

Спочатку незвично, і причина, чому це так, слушна: дві мажорні версії можуть співіснувати в одній збірці, тож транзитивна залежність на v1 не блокує ваш перехід на v2. Міграція може бути поступовою, а не одним критичним днем.

Проксі, база контрольних сум і приватний код

За замовчуванням go get завантажує через proxy.golang.org і перевіряє через sum.golang.org. Це дає вам доступність, коли оригінальний репозиторій зникає, і виявлення підміни.

Жоден із них не має бачити ваш приватний код. Одна змінна вирішує це:

export GOPRIVATE=gitlab.example.com/*,github.com/myorg/*

GOPRIVATE встановлює одразу GONOPROXY і GONOSUMDB, тож відповідні модулі завантажуються напряму через git і пропускаються при перевірці контрольної суми. Без цього приватне завантаження провалюється незрозуміло — і, що гірше, шлях модуля витікає до публічного сервісу.

Сканування вразливостей

go install golang.org/x/vuln/cmd/govulncheck@latest
govulncheck ./...

govulncheck кращий за звичайний сканер, бо використовує аналіз графа викликів: він повідомляє про вразливість, лише якщо ваш код справді досягає ураженої функції. Значно менше хибних спрацювань, тож вивід залишається вартим прочитання. Запускайте в CI.

Автоматичні оновлення

Dependabot і Renovate відкривають pull request'и для нових версій. Їх варто увімкнути за двох умов: ваш набір тестів має бути достатньо хорошим, щоб зелена збірка щось значила, і хтось справді має ці запити мержити. Репозиторій із дев'яноста відкритими PR щодо залежностей у гіршому стані, ніж репозиторій без жодного, бо справжнє оновлення безпеки похоронене серед інших.

Групуйте патч-оновлення, мажорні версії залишайте ручними.

Vendoring

go mod vendor

Копіює кожну залежність у vendor/, і збірка потім використовує виключно її. Іноді виправдано — збірка без доступу до мережі, жорстка вимога переглянути кожен байт, який ви постачаєте. Здебільшого кеш модулів і проксі вже вирішують ті проблеми, які мав вирішувати vendoring, а він додає великий каталог до кожного diff.

Заміна модуля

replace github.com/x/y => ../y
replace github.com/x/y => github.com/me/y v1.4.3-fork

Для локальної розробки одразу з двома модулями надавайте перевагу workspace — go.work не комітиться, тож не може потрапити в реліз. replace, що вказує на локальний шлях, зламає збірку всім іншим у той момент, коли його зіллють у гілку.

Тримайте кількість чесною

Кожна залежність — це код, який ви не писали, що виконується з вашими привілеями, і який може змінити хтось інший. Періодично:

go mod graph | wc -l

Якщо це число лише зростає, ніхто його не читає. Прибрати залежність, яку ви переросли — така сама підтримка, як і додати нову.

З досвіду Python: go.mod — це pyproject.toml, а go.sum — lock-файл, за винятком того, що тут немає virtualenv — залежності живуть у спільному кеші модулів, версійовані за шляхом, тож два проєкти ніколи не конфліктують. Вибір мінімальної версії — це протилежність резолверу pip: ви отримуєте найнижчу версію, що працює, а оновлення завжди навмисні.

Швидка довідка

Задача Команда
додати go get module@version
синхронізувати go.mod go mod tidy — і в CI теж
чому це тут go mod why <pkg>
оновити мінорні/патч go get -u ./...
видалити go get module@none
мажорні версії /v2, /v5 у шляху імпорту
приватні модулі GOPRIVATE=host/org/*
вразливості govulncheck ./...
локальна розробка workspace go.work, не replace
комітити і go.mod, і go.sum

Джерела