JavaRush /Курси /Go SELF /go.work — режим робочого простору

go.work — режим робочого простору

Go SELF
Рівень 35 , Лекція 4
Відкрита

1. Вступ

Коли ви пишете застосунок, він рідко існує у вакуумі. Майже завжди поруч є хоча б одна бібліотека: спільний пакет з утилітами, окремий модуль доменної логіки, внутрішній SDK або просто «винесений красиво в окремий репозиторій». І тут починається улюблена гра програмістів: «чому мій застосунок не бачить зміни в бібліотеці, хоча я щойно їх написав?».

До появи workspace mode типовий шлях був таким: або ви публікуєте зміни в бібліотеці, що часто зарано й незручно, або тимчасово додаєте в go.mod застосунку replace на локальну папку, а перед публікацією не забуваєте його прибрати. А «не забуваєте» — це, як ми знаємо, надто оптимістичний сценарій.

Workspace mode розвʼязує саме цю проблему: Go дозволяє працювати з кількома модулями одночасно, не змушуючи вас постійно редагувати go.mod кожного модуля заради локальної розробки. Ідея дуже проста: робочий простір дає змогу працювати з кількома модулями одночасно без потреби правити go.mod для кожного з них, а кожен модуль усередині workspace розглядається як «головний» під час розвʼязання залежностей.

2. Що таке workspace mode: ментальна модель

Важливо на хвилину зупинитися й домовитися про модель, інакше go.work сприйматиметься як ще один загадковий файл, який «треба десь створити, щоб усе запрацювало». А ми так не робимо: ми не шамани, ми інженери… ну, або принаймні намагаємося ними виглядати в очах компʼютера.

Workspace — це режим роботи інструментів Go, за якого вони враховують не один «головний модуль», а набір локальних модулів, перелічених у go.work. У результаті, коли один модуль імпортує пакети з іншого модуля, Go може брати їхні вихідні тексти прямо з диска, а не із завантаженої версії залежності.

З погляду інструмента go усе зводиться до того, що він читає go.work і отримує три речі: версію Go, список каталогів модулів і список замін. Це корисний якір: go.work не змінює синтаксис Go, він лише впливає на те, як інструменти розвʼязують залежності.

Намалюємо просту схему:

flowchart TD
    A[Ви запускаєте: go test / go run / go build] --> B[Go шукає контекст]
    B --> C{Workspace mode увімкнено?}
    C -->|ні| D[Працюємо як завжди: один основний модуль за go.mod]
    C -->|так| E[Читаємо go.work]
    E --> F[use: список модулів на диску]
    F --> G[Імпорти між цими модулями беремо з локальних вихідних текстів]

3. Файл go.work: як його читати

Файл go.work спеціально зроблено схожим на go.mod, щоб мозок не перегрівався. У ньому є директиви, і найважливіша для нас — use.

go.work містить директиви go, use і, за потреби, replace. Нам у цій лекції найважливіше зрозуміти саме use: вона додає до workspace модуль на диску, тобто каталог, де лежить go.mod.

Приклад мінімального go.work для Go 1.25:

// go.work
go 1.25

use (
    ./todoapp
    ./todolib
)

Тут немає жодних імпортів і «шляхів пакетів» — лише каталоги модулів. І це логічно: модуль визначається файлом go.mod, а go.work лише каже: «оці модулі вважай локальними й працюй із ними разом».

4. Приклад: todoapp + todolib в одному workspace

Продовжимо нашу навчальну лінію із застосунком задач (умовний todoapp). Уявімо, що на якомусь етапі ви вирішили винести модель та утиліти задач в окремий модуль todolib, бо «так красивіше» (і тому що ви майбутній автор бібліотеки, а не просто людина, яка пише main.go до кінця життя).

Зробімо структуру:

workspace/
  go.work
  todoapp/
    go.mod
    main.go
  todolib/
    go.mod
    task/
      task.go

todolib/go.mod: модуль-бібліотека

// todolib/go.mod
module example.com/todolib

go 1.25

Пакет task усередині todolib

Нехай у нас буде дуже скромна функція нормалізації заголовка задачі. Нічого героїчного — зате зрозуміло й життєво.

package task

import "strings"

func NormalizeTitle(s string) string {
	return strings.TrimSpace(s)
}

todoapp/go.mod: модуль-застосунок

// todoapp/go.mod
module example.com/todoapp

go 1.25

require example.com/todolib v0.0.0

Версія v0.0.0 тут — просто заглушка для прикладу (у реальності ви б вибрали конкретну версію або працювали б інакше). Зараз нам важлива ідея: застосунок залежить від модуля-бібліотеки.

Імпортуємо бібліотеку в todoapp/main.go

package main

import (
	"fmt"

	"example.com/todolib/task"
)

func main() {
	title := task.NormalizeTitle("   купити молоко   ")
	fmt.Println(title) // купити молоко
}

Якщо ви запускаєте todoapp без workspace, Go спробує знайти залежність example.com/todolib як модуль — завантажити її або взяти з кешу, залежно від оточення. А от якщо у вас є go.work і в ньому написано use ./todolib, то Go братиме example.com/todolib з локальної папки.

І тут починається найзручніше: ви змінюєте NormalizeTitle у todolib, одразу запускаєте todoapp — і застосунок відразу бачить зміни, без зайвих танців із replace у go.mod.

5. Створення й доповнення workspace

Коли ви вперше стикаєтеся з робочими просторами, виникає цілком буденне питання: «Окей, а як це взагалі запустити?». Можна вручну створити go.work, а можна попросити інструменти Go зробити це за вас.

Базовий шлях такий: робочий простір створюється командою go work init (можна з переліком каталогів модулів), а додавати модулі можна через go work use [moddir] або вручну редагувати файл. Важлива деталь: workspace не зобовʼязаний фізично містити модулі, з якими ви працюєте. Тобто go.work — це «мапа», а не «контейнер».

Якщо у вас монорепа, зручно жити так:

repo/
  go.work
  services/
    todoapp/     (go.mod)
    importtool/  (go.mod)
  libs/
    todolib/     (go.mod)
    textkit/     (go.mod)

А якщо у вас кілька репозиторіїв поряд на диску, ви можете зробити «робочу папку» (workspace directory), де лежить лише go.work, а модулі розташовані в сусідніх каталогах. Це особливо зручно, коли ви хочете виправити баг у бібліотеці й одразу перевірити його в застосунку, але водночас не перетворювати один репозиторій на звалище іншого.

Ще одна практична можливість — рекурсивне додавання модулів. Є варіант go work use -r ., який рекурсивно додає директорії з go.mod. Це зручно, якщо у вас багато модулів у дереві каталогів і ви не хочете перелічувати їх вручну.

6. Діагностика й обслуговування workspace

Як зрозуміти, що workspace mode увімкнено

Дуже типова ситуація: вчора все працювало, а сьогодні IDE підсвічує імпорти червоним, або go test раптом тягне не локальну бібліотеку, а якусь версію з мережі чи кешу. У такі моменти хочеться звинуватити всесвіт, ретроградний Меркурій і сусіда по Wi‑Fi. Але краще почати з простого: переконатися, що ви справді перебуваєте в workspace mode.

Інструменти Go орієнтуються на змінну оточення GOWORK: якщо вона вказує на файл, що закінчується на .work, workspace mode увімкнено. Щоб зрозуміти, який саме go.work використовується, можна виконати go env GOWORK: якщо вивід порожній, отже, workspace mode не активний.

Чому це важливо саме новачку? Тому що робочі простори — це не «частина коду», а «контекст запуску». А контекст запуску ламається найлегше: запустили команду не з тієї папки, відкрили в IDE не той корінь, термінал дивиться на інший проєкт — і от ви вже налагоджуєте не те, що думаєте.

go work sync: коли він потрібен

Команда go work sync звучить так, ніби вона має лагодити життя, стосунки й залежності. На практиці вона робить дещо конкретніше: «проштовхує» залежності з go.work назад у go.mod файлів модулів workspace.

Коли це може знадобитися? Уявіть, що ви додали в одному модулі новий імпорт зовнішнього пакета, Go підтягнув залежність, але в іншому модулі workspace тепер потрібен узгоджений стан. Іноді інструменти, IDE або перевірки хочуть, щоб go.mod модулів був приведений до очікуваного вигляду. Тоді sync допомагає привести все до узгодженого стану.

Але важливо не сприймати sync як «натиснув — і більше нічого не розумію». Будь-яка команда, яка змінює go.mod, заслуговує на повагу. Якщо коротко: go work sync корисний, коли ви свідомо ведете кілька модулів і хочете, щоб їхні go.mod відображали стан workspace, а не жили кожен своїм життям.

7. Сценарії multi‑module розробки з go.work

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

«Я правлю бібліотеку й одразу перевіряю її в застосунку»

Це найчастіший сценарій. Ви робите зміну в todolib, а потім запускаєте todoapp. У workspace mode застосунок використовує локальні вихідні тексти todolib замість опублікованої версії. Саме для цього workspaces і створено: ви можете працювати одразу з кількома модулями без потреби використовувати replace у go.mod і потім не забувати його видаляти.

Монорепозиторій із кількома модулями

Якщо у вас монорепа, то go.work стає «точкою збірки» для локальної розробки. Ви можете тримати кілька модулів, але водночас мати єдиний простір, де IDE коректно переходить до визначень, а go test ./... у потрібному контексті справді бачить увесь ліс, а не одне дерево.

Перемикання конфігурацій залежностей

Іноді потрібно перевірити проєкт у різних умовах: «а що буде, якщо бібліотека береться локально?» і «а що буде, якщо вона береться як звичайна залежність?». Workspaces дають змогу тримати різні go.work у різних папках або навіть один go.work, де ви тимчасово коментуєте або прибираєте use для деяких модулів.

Невелика таблиця, щоб закріпити очима різницю підходів:

Задача replace у go.mod go.work
Швидко «підкласти» локальну версію залежності в одному модулі Працює, але треба не забути прибрати Не треба правити go.mod модулів заради локальної розробки
Одночасно розвивати 2–5 модулів і проганяти тести разом Часто перетворюється на набір тимчасових replace Це «рідний» сценарій workspaces
Зрозуміти, чому в колеги не збирається У колеги може не бути такого самого шляху на диску У колеги може не бути workspace, або він може бути іншим, але це простіше діагностувати через GOWORK

8. Типові помилки під час роботи з go.work

Помилка № 1: додали в use не корінь модуля, а підпапку пакета.
Дуже спокусливо написати use ./todolib/task, бо вам потрібен пакет task. Але workspace працює на рівні модулів, а модуль визначається файлом go.mod. Тому в use має бути каталог, де лежить go.mod. Інакше ви отримаєте дивні симптоми: імпорти не знаходяться, IDE не розуміє структуру, а ви починаєте підозрювати, що Go просто вередує, хоча він лише буквально дотримується правил.

Помилка № 2: очікувати, що go.work «замінює» go.mod.
go.work — це не «новий go.mod», а зовнішній контекст для кількох модулів. У кожного модуля все одно залишається свій go.mod, і це його «паспорт». Workspace лише каже інструментам Go: «оці паспорти розглядай одночасно».

Помилка № 3: не перевірити, чи ввімкнено workspace mode, і налагоджувати не ту реальність.
Якщо GOWORK не вказує на .work-файл, workspace mode не активний, і Go працюватиме як зазвичай. Перевірка go env GOWORK — порожньо чи не порожньо — часто економить години життя й пару нервових клітин.

Помилка № 4: тримати go.work як «вічне глобальне налаштування» й забути, що він впливає на збірку.
go.work — це інструмент розробки. Він зручний, але водночас змінює контекст: залежності можуть братися локально. Це чудово, поки ви свідомо працюєте в розробці. Але якщо ви робите перевірки, збірки або ділитеся інструкціями з командою, важливо розуміти, чи враховується workspace і чи однаковий він у всіх.

Помилка № 5: сприймати go work sync як «полагодити все».
go work sync справді корисний, але він не магічний. Він змінює go.mod файлів модулів, тобто впливає на контракт залежностей. Використовуйте його тоді, коли розумієте, що саме хочете синхронізувати, а не коли «щось не працює, тож я натиснув кнопку».

1
Опитування
Модулі Go, рівень 35, лекція 4
Недоступний
Модулі Go
Модулі Go
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ