JavaRush /Курсы /Go SELF /go.work — workspace mode

go.work — workspace mode

Go SELF
35 уровень , 4 лекция
Открыта

1. Введение

Когда вы пишете приложение, оно редко живёт в вакууме. Почти всегда есть хотя бы одна библиотека, которую вы разрабатываете рядом: общий пакет с утилитами, отдельный модуль доменной логики, внутренний SDK или просто «вынес красиво в отдельный репозиторий». И тут начинается любимая игра программистов: «почему моё приложение не видит изменения в библиотеке, хотя я только что их написал».

До workspace mode типичный путь был такой: либо вы публикуете изменения в библиотеке (что часто рано и неудобно), либо временно добавляете в go.mod приложения replace на локальную папку, а перед публикацией не забываете его убрать. А «не забываете» — это, как мы знаем, оптимистичный сценарий.

Workspace mode решает именно эту боль: Go позволяет работать с несколькими модулями одновременно, не заставляя вас постоянно редактировать go.mod каждого модуля ради локальной разработки. Идея формулируется очень прямолинейно: workspaces позволяют работать с несколькими модулями одновременно без необходимости править 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[Работаем как обычно: один main module по 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("   buy milk   ")
	fmt.Println(title) // buy milk
}

Если вы запускаете todoapp без workspace, Go попытается найти зависимость example.com/todolib как модуль (скачать или взять из кеша, в зависимости от окружения). А вот если у вас есть go.work и в нём написано use ./todolib, то Go будет брать example.com/todolib из локальной папки.

И вот здесь рождается кайф: вы меняете NormalizeTitle в todolib, тут же запускаете todoapp — и приложение сразу видит изменения, без плясок с replace в go.mod.

5. Создание и пополнение workspace

Когда вы впервые сталкиваетесь с workspaces, возникает бытовой вопрос: «Окей, а как это вообще завести?». Можно руками создать go.work, а можно попросить инструменты Go сделать это за вас.

Базовый путь такой: workspace создаётся командой 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 tools ориентируются на переменную окружения goWORK: если она указывает на файл, оканчивающийся на .work, workspace mode включён. Чтобы понять, какой go.work используется, можно выполнить go env GOWORK: если вывод пустой, значит workspace mode не активен.

Почему это важно именно новичку? Потому что workspaces — это не «часть кода», это «контекст запуска». А контекст запуска ломается легче всего: запустили команду не из той папки, открыли в 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 SELF, 35 уровень, 4 лекция
Недоступна
Два модуля
Два модуля
1
Задача
Go SELF, 35 уровень, 4 лекция
Недоступна
Неправильный use
Неправильный use
1
Опрос
Go Modules, 35 уровень, 4 лекция
Недоступен
Go Modules
Go Modules
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ