1. build.gradle.kts: czytanie po blokach
W tym momencie mamy już trzy filary: plugins{} zmieniają zachowanie budowania, repositories{} mówią, gdzie szukać bibliotek, dependencies{} wymienia, co projekt potrzebuje. Teraz nie chodzi nam o nową dawkę teorii, ale o umiejętność szybkiego orientowania się w pliku: gdzie teraz jesteś i jaki blok w ogóle edytujesz.
Dla orientacji trzymaj taką „mapę” pliku:
| Blok | Na jakie pytanie odpowiada | Prosta asocjacja |
|---|---|---|
|
„Jakie możliwości ma budowanie?” | jakie narzędzia leżą w skrzynce |
|
„Skąd pobrać biblioteki?” | do jakiego sklepu idziemy |
|
„Co dokładnie pobieramy i gdzie kładziemy?” | co kupujemy i na jaką półkę w domu |
Minimalny „szkielet” wygląda mniej więcej tak:
plugins {
// Podstawowe możliwości projektu Java (kompilacja, testy itp.)
java
// Możliwość uruchamiania aplikacji przez `gradlew run`
application
}
repositories {
// Maven Central — główne publiczne repozytorium bibliotek Java
mavenCentral()
}
dependencies {
// Na razie pusto — ale niedługo: dodamy tu biblioteki projektu
}
Warto zauważyć pewien subtelny moment: dependencies{} sam w sobie „nie żyje osobno”. Konfiguracje typu implementation pojawiają się dlatego, że plugin Java wnosi standardowy model projektu Java. Ale przy edytowaniu zwykle wystarczy nam pamiętanie prostego obrazu: pluginy zadają zachowanie budowania, repozytoria – wyszukiwanie bibliotek, zależności – skład classpath.
2. Linijka zależności: rola i koordynaty
Jedna linijka w dependencies{} to tylko dwie rzeczy: rola zależności i jej koordynaty. Jeśli trzymać w głowie tę krótką formułę, edytowanie pliku build staje się znacznie spokojniejsze.
<konfiguracja>("group:artifact:version")
Na przykład:
dependencies {
// Jackson: biblioteka do pracy z JSON (potrzebna w głównym kodzie)
implementation("tools.jackson.core:jackson-databind:3.0.4")
}
Czyta się dosłownie tak: „do głównego kodu projektu (implementation) potrzebuję biblioteki o koordynatach tools.jackson.core:jackson-databind:3.0.4”.
W Kotlin DSL wygląda to jak wywołanie funkcji: implementation(...). Dlatego typowe błędy są tu zwykle czysto składniowe, jak w zwykłym kodzie: zapomniałeś cudzysłowu, zamykającego nawiasu, albo przypadkiem postawiłeś pojedyncze cudzysłowy zamiast podwójnych. Gradle w takim wypadku „nie zgadnie, co miałeś na myśli”, ale uczciwie powie: „nie rozumiem tego pliku”.
3. Edytowanie dependencies{} małymi krokami
Najczęstszy problem nowicjusza nie polega na tym, że „nie rozumie zależności”, ale na tym, że edytuje plik od razu na 20 linijek, potem Gradle pada, i zaczyna się zgadywanie „której z dwudziestu linijek nie przełknął”. Dlatego złota zasada dzisiejszego wykładu jest bardzo prosta: zmieniaj po jednej rzeczy naraz, a potem sprawdzaj, czy projekt nadal się składa i uruchamia.
Spokojny scenariusz roboczy zwykle wygląda tak. Otwierasz build.gradle.kts, znajdujesz dependencies{} (wyszukiwanie po słowie dependencies – nie ma się czego wstydzić, to profesjonalne lenistwo), dodajesz dokładnie jedną linijkę, zapisujesz plik, dajesz IDE podchwycić zmiany lub po prostu uruchamiasz znajomą komendę Gradle z Poziomu 2 (np. ./gradlew run). Jeśli wszystko ok – świetnie, idziemy dalej. Jeśli nie – wiesz dokładnie, która linijka jest winna, bo to właśnie ją dodałeś ostatnio.
Na przykład staranne dodanie jednej zależności może wyglądać tak:
dependencies {
// SLF4J — to API do logowania (implementację podłączymy osobno)
implementation("org.slf4j:slf4j-api:2.0.17")
}
Jeśli po tym Gradle zgłasza, że „nie może znaleźć zależności”, prawie zawsze oznacza to jedno z trzech: pomyliłeś się w koordynatach (literówka), zapomniałeś repozytorium (brak mavenCentral()) albo wziąłeś koordynaty nie tej wersji lub nie tej rodziny biblioteki. I to też jest miłe: problemów zwykle nie jest tysiąc, ale kilka typowych – tyle że w różnych opakowaniach.
Osobny mały nawyk, który szybko się opłaca: po edytowaniu build.gradle.kts, gdy wszystko działa, rób mały commit w Git. Nie dlatego, że „tak trzeba”, ale dlatego, że to najtańszy sposób mieć przycisk „wróć do stanu roboczego” bez łez i dramatów.
4. Czytelność dependencies{}: kolejność i grupy
Gradle zwykle nie obchodzi zbytnio kolejność linijek w dependencies{}, ale ludzi obchodzi bardzo. I to normalne: za tydzień otworzysz plik i będziesz chciał zrozumieć, co tu się dzieje, bez questu „znajdź Jacksona pośród chaosu”. Dlatego robimy blok zależności nie tylko działający, ale i czytelny – zwłaszcza w projekcie edukacyjnym, gdzie każdy kolejny poziom będzie opierał się na poprzednim.
Spójrz na „chaotyczny” wariant. Może działać, ale czytanie tego jest nieprzyjemne – jak książki, w której pomieszano strony:
dependencies {
// Przykład: wszystko pomieszane — Gradle przeżyje, a człowiek będzie cierpieć
runtimeOnly("ch.qos.logback:logback-classic:1.5.32")
implementation("tools.jackson.core:jackson-databind:3.0.4")
implementation("org.slf4j:slf4j-api:2.0.17")
implementation("tools.jackson.core:jackson-core:3.0.4")
runtimeOnly("ch.qos.logback:logback-core:1.5.32")
}
A teraz ten sam sens, ale po ludzku: podobne biblioteki stoją obok siebie, są małe komentarze-separatory i pusta linia między grupami sensowymi. To już nie „magia”, ale dokument:
dependencies {
// JSON
implementation("tools.jackson.core:jackson-core:3.0.4")
implementation("tools.jackson.core:jackson-databind:3.0.4")
// Logging
implementation("org.slf4j:slf4j-api:2.0.17") // API: na nie będzie się odwoływał kod
runtimeOnly("ch.qos.logback:logback-classic:1.5.32") // implementacja: potrzebna przy uruchamianiu
runtimeOnly("ch.qos.logback:logback-core:1.5.32") // w baseline kursu zostawiamy moduł jawnie obok classic
}
Ważne, żeby nie zamieniać komentarzy w powieść. Wystarczą krótkie znaczniki typu // JSON i // Logging. To jak podpisy na pudełkach przy przeprowadzce: nie piszesz „pudełko nr 7 z różnymi przedmiotami, które teoretycznie mogą się przydać”, piszesz „KUCHNIA” – i wszystkim od razu lżej.
Jeszcze jedna pożyteczna zasada: trzymaj „rodziny” bibliotek obok siebie. Jeśli masz dwie zależności z tego samego group, prawie na pewno są one logicznie powiązane. To nie absolutna reguła, ale w projekcie edukacyjnym działa znakomicie i czyni strukturę bloku oczywistą.
5. Mini-case: ReadLater Starter i wzrost zależności
W ReadLater Starter celowo idziemy krok po kroku: najpierw szkielet, potem podstawowa znajomość zależności, potem już realne biblioteki. Teraz naszym celem nie jest „podłączyć wszystko, co tylko można”, ale przygotować dependencies{} tak, żeby jutro i za tydzień nadal wyglądał schludnie i wytłumaczalnie.
Wyobraź sobie, że otworzyłeś plik i chcesz zrozumieć dwie rzeczy: „co dotyczy JSON?” i „co dotyczy logów?”. Jeśli blok jest już zgrupowany, odpowiedź znajduje się w 3 sekundy. Jeśli wszystko pomieszane, zaczynasz biegać oczami po pliku i tracić energię nie na rozumienie tematu backendu, a na szukanie potrzebnych linijek.
Dlatego w ramach ReadLater Starter pożytecznie trzymać blok zależności w postaci małej mapy. Nawet jeśli dziś dodałeś tylko jedną bibliotekę, możesz od razu zostawić strukturę-szablon, żeby nie składać jej potem w pośpiechu. Na przykład taki szkielet jest jak najbardziej w porządku:
dependencies {
// Tutaj będą zależności do pracy z JSON
// Tutaj będą zależności do logowania
// Zależności testowe (na razie pusto)
}
Tak, wygląda to jak puste pudełka. Ale to uczciwie puste pudełka, które za parę wykładów staną się treściwe – a plik zachowa czytelność. I jest tu jeszcze mały praktyczny moment: zaczynasz widzieć projekt nie jako „zestaw linijek”, ale jako strukturę, którą kontrolujesz.
6. Typowe błędy przy edycji dependencies{}
Błędy w dependencies{} zwykle nie są „skomplikowane”, ale jak najbardziej ludzkie: spieszymy się, kopiujemy kawałek z internetu, nie doczytujemy, gdzie powinien być, i dostajemy czerwony ekran Gradle. To normalne. Ważne, żeby nauczyć się nie bać takich błędów i szybko rozumieć, co konkretnie się zepsuło – prawie jak w zwykłym kodzie Java.
Błąd nr 1: próbować wpisywać zależności w blok plugins{}.
To klasyczne pomyłka: zobaczyłeś linijkę postaci something("group:artifact:version") i uznałeś, że to „też o pluginach”. Ale zasada jest prosta: pluginy opisują zachowanie budowania, a biblioteki dla kodu – w dependencies{}. Jeśli kładziesz bibliotekę w plugins, Gradle nie będzie „się domyślać” – po prostu odmówi to rozumieć.
Błąd nr 2: edytować pół pliku za jednym podejściem, a potem nie wiedzieć, która linijka wszystko zepsuła.
Gdy od razu dodajesz pięć bibliotek, zmieniasz kolejność, komentujesz stare i przy okazji edytujesz repozytoria, sam pozbawiasz się normalnej diagnostyki. Jeden mały krok – jedno zrozumiałe sprawdzenie. To znacznie nudniejsze, ale zato nie zamienia build w loterię.
Błąd nr 3: dublować zależność albo zostawiać starą wersję obok „na wszelki wypadek”.
W ten sposób w pliku szybko pojawiają się prawie identyczne linijki i za parę dni nie wiadomo już, która jest robocza, a która – przypadkowy ogon. Dobry nawyk jest prosty: po edycji przeczytaj cały blok jeszcze raz i usuń wszystko, co stało się zbędne.
Błąd nr 4: używać runtimeOnly, a potem importować klasy tej biblioteki w głównym kodzie.
To subtelna pułapka. runtimeOnly oznacza: biblioteka będzie na classpath przy uruchamianiu, ale niekoniecznie przy kompilacji. Jeśli spróbujesz napisać import ... z takiej zależności, kompilacja może paść. Jeśli biblioteka jest potrzebna w kodzie bezpośrednio, jej miejsce jest w implementation.
Błąd nr 5: edytować blok bez patrzenia na jego czytelność.
Gradle być może przeżyje chaos z linijek, ale człowiek – nie zawsze. Jeśli po edycji sam nie potrafisz w 10 sekund wyjaśnić, gdzie u ciebie JSON, gdzie logging i dlaczego biblioteki są rozłożone właśnie tak, znaczy że blok już zaczyna działać przeciwko tobie.
GO TO FULL VERSION