Вибір та управління залежностями¶
Усе, про що йдеться далі, потрапляє в go.mod. Решта цієї книги — це
мова та її стандартна бібліотека, які залишаються незмінними, що б ви
не будували; цей розділ — це вибір одного конкретного стеку, а вибір
потребує підтримки.
Оцініть модуль перед тим, як додати його¶
Залежність — це постійне зобов'язання покладатися на чужу думку. Варте двох хвилин:
- Чи його підтримують? Свіжі коміти, відповіді на 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, мажорна версія стає частиною шляху імпорту:
Спочатку незвично, і причина, чому це так, слушна: дві мажорні версії можуть співіснувати в одній збірці, тож транзитивна залежність на v1 не блокує ваш перехід на v2. Міграція може бути поступовою, а не одним критичним днем.
Проксі, база контрольних сум і приватний код¶
За замовчуванням go get завантажує через proxy.golang.org і
перевіряє через sum.golang.org. Це дає вам доступність, коли
оригінальний репозиторій зникає, і виявлення підміни.
Жоден із них не має бачити ваш приватний код. Одна змінна вирішує це:
GOPRIVATE встановлює одразу GONOPROXY і GONOSUMDB, тож відповідні
модулі завантажуються напряму через git і пропускаються при перевірці
контрольної суми. Без цього приватне завантаження провалюється
незрозуміло — і, що гірше, шлях модуля витікає до публічного сервісу.
Сканування вразливостей¶
govulncheck кращий за звичайний сканер, бо використовує аналіз графа
викликів: він повідомляє про вразливість, лише якщо ваш код справді
досягає ураженої функції. Значно менше хибних спрацювань, тож вивід
залишається вартим прочитання. Запускайте в CI.
Автоматичні оновлення¶
Dependabot і Renovate відкривають pull request'и для нових версій. Їх варто увімкнути за двох умов: ваш набір тестів має бути достатньо хорошим, щоб зелена збірка щось значила, і хтось справді має ці запити мержити. Репозиторій із дев'яноста відкритими PR щодо залежностей у гіршому стані, ніж репозиторій без жодного, бо справжнє оновлення безпеки похоронене серед інших.
Групуйте патч-оновлення, мажорні версії залишайте ручними.
Vendoring¶
Копіює кожну залежність у vendor/, і збірка потім використовує
виключно її. Іноді виправдано — збірка без доступу до мережі, жорстка
вимога переглянути кожен байт, який ви постачаєте. Здебільшого кеш
модулів і проксі вже вирішують ті проблеми, які мав вирішувати
vendoring, а він додає великий каталог до кожного diff.
Заміна модуля¶
Для локальної розробки одразу з двома модулями надавайте перевагу
workspace —
go.work не комітиться, тож не може потрапити в реліз. replace, що
вказує на локальний шлях, зламає збірку всім іншим у той момент, коли
його зіллють у гілку.
Тримайте кількість чесною¶
Кожна залежність — це код, який ви не писали, що виконується з вашими привілеями, і який може змінити хтось інший. Періодично:
Якщо це число лише зростає, ніхто його не читає. Прибрати залежність, яку ви переросли — така сама підтримка, як і додати нову.
З досвіду 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 |