JavaRush /Курсы /Java Server /Воспроизводимый каркас проекта

Воспроизводимый каркас проекта

Java Server
2 уровень , 4 лекция
Открыта

1. Definition of Done: каркас готов

Каркас проекта можно считать готовым, когда он воспроизводимо запускается не только «как‑то у меня на машине». Это важнее, чем кажется, потому что backend‑разработка почти всегда командная: вы будете передавать код другому человеку, переносить проект между компьютерами и окружениями, а иногда и сами через неделю забудете, какие шаманские настройки в IDE накрутили.

Представьте, что ваш проект — это рецепт. Можно один раз приготовить блюдо «на глаз» и сказать: «получилось вкусно». Но если вы хотите, чтобы его можно было повторить, рецепт должен быть записан, ингредиенты — перечислены, температура — указана. Воспроизводимый каркас — это как раз «рецепт запуска», записанный внутри самого репозитория.

Нам нужен каркас, который:

  1. одинаково стартует на macOS/Linux и Windows (с поправкой на gradlew vs gradlew.bat),
  2. не требует угадывать, что нажимать в IDE,
  3. содержит минимальные, но обязательные файлы, которые делают проект проектом, а не папкой со случайными .java.

Основная механика уже собрана. Осталось зафиксировать, что именно должно лежать в репозитории, что туда попадать не должно и по каким признакам каркас можно считать воспроизводимым.

Чтобы визуально закрепить идею каркаса и воспроизводимости, удобно думать так:

flowchart TD
    A[Новый человек видит репозиторий впервые] --> B[git clone]
    B --> C[переходит в корень проекта]
    C --> D[./gradlew build]
    D --> E[./gradlew run]
    E --> F[получает ожидаемый вывод]

Если эта цепочка работает — каркас можно считать готовым. Если нет, это ещё «не проект», даже если IDE на вашей машине бодро запускает main().

2. Минимальное дерево проекта

Когда мы говорим «каркас», у многих в голове всплывает что‑то абстрактное: мол, «ну папки какие‑то». На практике каркас — это вполне конкретное дерево файлов, на которое можно показать пальцем. У этого дерева есть простое свойство: оно объясняет, где вход, где правила сборки, где код, где ресурсы и где инструкция для человека.

Ниже — тот же каркас ReadLater Starter, который мы уже собрали и запускали. Здесь важно не придумывать новую версию проекта, а проверить, что репозиторий выглядит именно так — плюс есть .gitignore для чистой истории Git.


readlater-starter/
├── gradlew
├── gradlew.bat
├── gradle/
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── settings.gradle.kts
├── build.gradle.kts
├── README.md
├── .gitignore
└── src/
    └── main/
        ├── java/
        │   └── com/
        │       └── example/
        │           └── readlater/
        │               ├── ReadLaterApplication.java
        │               └── ConsoleBanner.java
        └── resources/
            └── application.properties

Если папка src/main/resources пока пустая, её иногда временно фиксируют через .gitkeep. У нас она уже не пустая: в ней лежит application.properties, так что отдельный .gitkeep здесь не нужен.

Чтобы не превращать это в «угадайку», давайте зафиксируем смысл каждого элемента каркаса в короткой таблице.

Элемент Зачем нужен Как понять, что он на месте
gradlew
,
gradlew.bat
Единая точка входа в Gradle на разных ОС Команды
./gradlew build
и
gradlew.bat build
реально работают
gradle/wrapper/*
Фиксация версии Gradle и механика загрузки В
gradle-wrapper.properties
зафиксирован
gradle-9.4.0-bin.zip
settings.gradle.kts
Имя сборки и «границы» проекта
rootProject.name
задан как
readlater-starter
build.gradle.kts
Правила сборки и запуска Есть
java
,
application
,
mavenCentral()
, Java 25 toolchain и
mainClass
src/main/java
Стандартное место исходников На месте
ReadLaterApplication.java
и
ConsoleBanner.java
src/main/resources
Стандартное место не‑Java файлов Папка существует, и в ней лежит
application.properties
README.md
Инструкция запуска для человека В README есть Wrapper‑команды для сборки и запуска
.gitignore
Защита от мусора в репозитории
build/
,
.gradle/
,
.idea/
не попадают в коммиты

Wrapper — часть репозитория

Здесь мы уже не разбираем Wrapper заново, а просто проверяем, что он на месте. В репозитории должны лежать gradlew, gradlew.bat и gradle/wrapper/*; именно через них проект получает свою фиксированную версию Gradle. Эти файлы приходят вместе со стартовым каркасом и дальше живут в Git как часть проекта, а не как локальный мусор.

settings.gradle.kts: имя сборки

settings.gradle.kts остаётся коротким: rootProject.name = "readlater-starter". На финише дня здесь не нужна новая теория; нужна проверка, что имя сборки совпадает с тем, как вы называете проект в папке, README и у себя в голове.

build.gradle.kts: тот же baseline

В build.gradle.kts мы ничего заново не изобретаем. Остаются java и application, mavenCentral(), Java 25 toolchain и mainClass = "com.example.readlater.ReadLaterApplication". Если к концу дня у вас внезапно появился другой «более правильный» файл, чаще всего это просто разъехавшийся каркас.

src/main/java: один официальный main

ReadLaterApplication.java и ConsoleBanner.java остаются теми же. Нам нужен один понятный main() и один видимый стартовый маркер приложения, чтобы ./gradlew run проверял реальный запуск, а не гадал, какой класс вы хотели стартовать.

src/main/resources: папка уже не пустая

src/main/resources/ в нашем каркасе уже содержит application.properties, поэтому отдельный .gitkeep здесь не нужен. Если в каком‑то другом проекте папка временно пустая, .gitkeep — нормальный технический приём, но здесь роль якоря уже выполняет реальный ресурс.

README: инструкция запуска

В README.md должны быть записаны команды сборки и запуска. Не «откройте IDE и нажмите Run», а нормальные Wrapper‑команды, которые человек видит сразу после git clone.

.gitignore: пусть мусор не становится историей

Сборка создаёт артефакты, и это нормально. Ненормально, когда эти артефакты коммитятся. Для старта достаточно простого .gitignore:

# Gradle
# Служебные каталоги Gradle: не имеют смысла в истории репозитория
.gradle/
build/

# IDE
# Локальные настройки IDE у каждого свои — их не нужно тащить в общий репозиторий
.idea/
*.iml
out/

Да, .gitignore — это не «про Gradle», а про здравый смысл. Иначе история Git быстро превращается в свалку временных файлов.

3. Согласованные имена проекта

После дерева файлов полезно быстро проверить, что договорённости об именах не разъехались. В начале пути кажется, что названия — это косметика. Но на самом деле названия — это навигация по проекту, и она либо помогает, либо мстит. Самая частая боль новичка: «почему Gradle не находит mainClass», «почему package не совпадает с папками», «почему проект везде называется по‑разному». Это не сложные ошибки — это просто несогласованные договорённости.

Здесь полезно ввести простое правило: у проекта есть несколько «имён», и они должны быть дружелюбно согласованы между собой. Не обязательно идентичны до символа, но без противоречий. Минимально у нас есть имя папки проекта, rootProject.name, base package и имя main‑класса.

Давайте зафиксируем это на ReadLater Starter:

«Где написано имя» Пример Зачем важно
Папка проекта
readlater-starter/
Чтобы человек понимал, куда он вошёл
Имя сборки Gradle
rootProject.name = "readlater-starter"
Чтобы Gradle‑мир называл проект так же, как вы
Java package
com.example.readlater
Чтобы код лежал предсказуемо, а импорты были ясными
Точка входа
ReadLaterApplication
Чтобы было понятно, что именно запускать

Заметьте, что ReadLaterApplication мы называем «application» не потому, что так принято вообще везде, а потому что нам нужна одна точка входа в один артефакт. Когда позже появится несколько режимов запуска — не сегодня, — будет приятно иметь один класс, который отвечает именно за старт.

Ещё один нюанс, который часто всплывает: путь до файла должен соответствовать package. Если package — com.example.readlater, то файл должен лежать в src/main/java/com/example/readlater/ReadLaterApplication.java. Не потому, что «Gradle так хочет» — хотя и поэтому тоже, — а потому, что так вы не превращаете проект в лабиринт, где каждый класс лежит в своём личном уголке.

4. Единственный способ запуска: команды

С командной частью мы уже разобрались; здесь важно зафиксировать одно правило репозитория: единственный официальный способ запуска — через Wrapper из корня проекта. Эти же команды должны лежать в README, и именно ими должен пользоваться любой новый разработчик.

Команды такие:

# macOS / Linux
./gradlew build
./gradlew run

Для Windows:

:: Windows (cmd)
gradlew.bat build
gradlew.bat run

Если вы используете PowerShell, может понадобиться форма .\gradlew.bat build и .\gradlew.bat run. Смысл не меняется: запускаем именно Wrapper из корня проекта.

Если при первом запуске Wrapper что‑то скачивает, это нормально. Если на macOS/Linux вы видите Permission denied, один раз делается:

chmod +x gradlew

И ещё один важный стоп‑сигнал: если вам хочется вбить gradle build вместо ./gradlew build, остановитесь. В нашем baseline это уже другой источник истины.

5. Самопроверка

Когда каркас только что собран, мозг легко обманывается: «ну, вроде бы всё есть». Хорошая инженерная привычка — делать короткую самопроверку, которая занимает минуту, но экономит час будущей боли. Это особенно полезно в учебном проекте: сегодня всё маленькое, а завтра к проекту начнут добавляться новые файлы и новая ответственность.

Ниже — компактная проверка воспроизводимого каркаса без глубоких знаний Gradle. Она не требует понимать, что такое task graph, classpath и прочие вещи. Она требует только честно ответить: «это работает на чистом запуске из терминала?»

Мини‑чек в виде таблицы

Проверка Что сделать Что считается успехом
Wrapper на месте Открыть корень проекта Видны
gradlew
,
gradlew.bat
,
gradle/wrapper
Версия Gradle фиксирована Открыть
gradle-wrapper.properties
В
distributionUrl
стоит
gradle-9.4.0-...
Проект собирается
./gradlew build
Команда завершается успешно, появляется
build/
Проект запускается
./gradlew run
В консоли виден стартовый маркер приложения
README описывает старт Открыть
README.md
Есть Wrapper‑команды для macOS/Linux и Windows
Репозиторий не тащит мусор Открыть
.gitignore
и
git status
build/
,
.gradle/
,
.idea/
не летят в коммиты

Что вы должны увидеть при запуске

Мы не привязываемся к точному выводу Gradle по символам, но «видимый маркер» вашего приложения должен появиться. Например:

> Task :run
=== ReadLater Starter ===
ReadLater Starter is running

Форма вывода может чуть отличаться в зависимости от платформы и версии консоли, но баннер и сообщение старта должны быть видны.

И ещё один важный визуальный признак: после build у вас появляется папка build/. Это не «лишняя папка», а результат работы Gradle. Её не нужно удалять руками после каждого запуска — Gradle сам управляет жизненным циклом артефактов, — и её не нужно коммитить.

Если хочется мысленно «прощупать», что именно делает воспроизводимость воспроизводимостью, держите в голове такую короткую цепочку:

sequenceDiagram
    participant Dev as Разработчик
    participant Repo as Репозиторий
    participant W as Gradle Wrapper
    participant G as Gradle 9.4.0
    participant App as ReadLaterApplication

    Dev->>Repo: Открывает проект
    Dev->>W: Запускает ./gradlew run
    W->>G: Использует зафиксированную версию Gradle
    G->>App: Компилирует и запускает mainClass
    App-->>Dev: Печатает стартовый маркер приложения

Вот это и есть «готовый каркас»: цепочка запуска описана проектом, а не вашим настроением и не настройками IDE.

6. Типичные ошибки при финальной проверке каркаса

Ошибка №1: проект запускается только из IDE, а через ./gradlew run — нет.
Если IDE «догадалась» сама, какой main() запускать, а Wrapper — нет, значит, знание о запуске живёт не в проекте. Пока ./gradlew run не работает, Definition of Done не закрыт, даже если зелёный треугольник в IDE бодро машет вам лапкой.

Ошибка №2: Wrapper‑файлы не закоммичены или были удалены «как мусор».
Иногда новичок видит gradle-wrapper.jar и думает: «фу, бинарник, удалю». В итоге он удаляет основу воспроизводимости. Wrapper — это часть проекта. Его присутствие в репозитории не позор, а признак того, что проект реально можно запустить на другой машине.

Ошибка №3: build/, .gradle/ и IDE‑артефакты попали в Git.
Это значит, что проект тащит в историю не исходники и договорённости, а локальный мусор сборки. Один раз настроенный .gitignore обычно снимает эту боль почти полностью. Если в git status у вас после build полстраницы временных файлов, проблема не в Git, а в гигиене репозитория.

Ошибка №4: команды запуска нигде не записаны, кроме вашей памяти.
Если в README.md нет команд ./gradlew build / ./gradlew run и Windows‑варианта, новый человек снова будет зависеть от устных подсказок. Это то самое состояние «у меня работает, но я сейчас объясню голосом». Для воспроизводимого каркаса это уже провал.

Ошибка №5: вместо Wrapper продолжают использовать системный gradle.
Команда gradle build кажется безобидной, пока не выяснится, что у другого человека стоит другая версия Gradle или его нет вообще. В нашем проекте официальный вход один: ./gradlew или gradlew.bat. Всё остальное — лишний источник расхождений.

1
Задача
Java Server, 2 уровень, 4 лекция
Недоступна
Чистый reproducible skeleton с `.gitignore`
Чистый reproducible skeleton с `.gitignore`
1
Задача
Java Server, 2 уровень, 4 лекция
Недоступна
Локальное клонирование и запуск из копии репозитория
Локальное клонирование и запуск из копии репозитория
1
Опрос
Сборка проекта, 2 уровень, 4 лекция
Недоступен
Сборка проекта
Gradle и структура проекта
Комментарии (1)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
14 мая 2026
Задача Java Server, 2 уровень, 4 лекция Локальное клонирование и запуск из копии репозитория Ошибка: Usage: javac <options> <source files> означает, что javac получил сломанный путь к файлам. Вот эта строка подозрительная: ujavarushjavaspring01level02sersask10echo-startersrcmainjavacomexampleechoEchoApplication.java Сломан gradlew.bat Stroka 45-51 zamenit na -> REM Список исходников складываем в argfile, чтобы не раздувать команду javac и не зависеть от длины строки. > "%BUILD_DIR%\sources.list" ( for /r "%PROJECT_DIR%src\main\java" %%F in (*.java) do @echo %%F REM Сломан gradlew.bat for /r "%PROJECT_DIR%src\main\java" %%F in (*.java) do echo "%%~fF" )