1. Definition of Done: каркас готов
Каркас проекта можно считать готовым, когда он воспроизводимо запускается не только «как‑то у меня на машине». Это важнее, чем кажется, потому что backend‑разработка почти всегда командная: вы будете передавать код другому человеку, переносить проект между компьютерами и окружениями, а иногда и сами через неделю забудете, какие шаманские настройки в IDE накрутили.
Представьте, что ваш проект — это рецепт. Можно один раз приготовить блюдо «на глаз» и сказать: «получилось вкусно». Но если вы хотите, чтобы его можно было повторить, рецепт должен быть записан, ингредиенты — перечислены, температура — указана. Воспроизводимый каркас — это как раз «рецепт запуска», записанный внутри самого репозитория.
Нам нужен каркас, который:
- одинаково стартует на macOS/Linux и Windows (с поправкой на gradlew vs gradlew.bat),
- не требует угадывать, что нажимать в IDE,
- содержит минимальные, но обязательные файлы, которые делают проект проектом, а не папкой со случайными .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 здесь не нужен.
Чтобы не превращать это в «угадайку», давайте зафиксируем смысл каждого элемента каркаса в короткой таблице.
| Элемент | Зачем нужен | Как понять, что он на месте |
|---|---|---|
, |
Единая точка входа в Gradle на разных ОС | Команды и реально работают |
|
Фиксация версии Gradle и механика загрузки | В зафиксирован |
|
Имя сборки и «границы» проекта | задан как |
|
Правила сборки и запуска | Есть , , , Java 25 toolchain и |
|
Стандартное место исходников | На месте и |
|
Стандартное место не‑Java файлов | Папка существует, и в ней лежит |
|
Инструкция запуска для человека | В README есть Wrapper‑команды для сборки и запуска |
|
Защита от мусора в репозитории | , , не попадают в коммиты |
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:
| «Где написано имя» | Пример | Зачем важно |
|---|---|---|
| Папка проекта | |
Чтобы человек понимал, куда он вошёл |
| Имя сборки Gradle | |
Чтобы Gradle‑мир называл проект так же, как вы |
| Java package | |
Чтобы код лежал предсказуемо, а импорты были ясными |
| Точка входа | |
Чтобы было понятно, что именно запускать |
Заметьте, что 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 на месте | Открыть корень проекта | Видны , , |
| Версия Gradle фиксирована | Открыть |
В стоит |
| Проект собирается | |
Команда завершается успешно, появляется |
| Проект запускается | |
В консоли виден стартовый маркер приложения |
| README описывает старт | Открыть |
Есть Wrapper‑команды для macOS/Linux и Windows |
| Репозиторий не тащит мусор | Открыть и |
, , не летят в коммиты |
Что вы должны увидеть при запуске
Мы не привязываемся к точному выводу 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. Всё остальное — лишний источник расхождений.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ