Projekti ülesehitus
Sissejuhatus
Üks asi on teada, mis paketid ja moodulid on, teine asi on nende hea ja korrektne kasutus.
Mida suuremaks projekt kasvab, seda raskemaks muutub selle haldamine. Faile on raske leida, klassidevahelised sõltuvused lähevad sassi ning ühe koodirea muutmine võib ootamatult mujal midagi katki teha.
Hea projekt algab korrektsest ülesehitusest. See tähendab, et koodibaasi on:
- lihtne navigeerida
- lihtne mõista
- lihtne muuta
Struktuur ei ole ainult maitse küsimus, see mõjutab otseselt arenduse kiirust ja vigade hulka.
Probleem ilma struktuurita
Vaatleme väikest ülikoolisüsteemi, kus kõik klassid asuvad samas kaustas ilma pakettideta:
src/
├── Student.java
├── Course.java
├── Enrollment.java
├── Teacher.java
├── AdminOffice.java
├── EmailSender.java
├── DatabaseHelper.java
├── DateUtils.java
└── Main.java
Alguses pole probleemi, projekt on piisavalt väike. Kuid kui projekt kasvab 30, 50 või 100 klassini, kerkivad esile mitmed probleemid:
- Konkreetse klassi leidmiseks tuleb kogu nimekiri läbi vaadata.
- Ei ole selge, millised klassid omavahel kokku kuuluvad.
- Muudatus ühes klassis võib ootamatult mõjutada paljusid teisi klasse.
- Puudub selge piir äriloogika ja abiklasside vahel.
Organiseerimine funktsionaalsuse järgi
Kõige loomulikum viis projekti organiseerimiseks on funktsionaalsuse järgi - grupeerides kokku klassid, mis kuuluvad samasse süsteemi ossa. Näiteks:
src/
└── university/
├── students/
│ ├── Student.java
│ └── Enrollment.java
├── courses/
│ ├── Course.java
│ └── Schedule.java
├── staff/
│ └── Teacher.java
├── administration/
│ └── AdminOffice.java
└── util/
├── EmailSender.java
├── DatabaseHelper.java
└── DateUtils.java
Struktuur peegeldab nüüd süsteemi mõtet.
Kui on vaja muuta tudengitega seotud funktsionaalsust, vaatad students/ pakki.
Tunniplaaniga probleemid - uurid courses/ pakki.
Kihipõhine organiseerimine
Alternatiivne lähenemine on organiseerimine kihipõhiselt, grupeerides klasse nende arhitektuurilise rolli järgi:
src/
└── university/
├── model/
│ ├── Student.java
│ ├── Course.java
│ └── Teacher.java
├── service/
│ ├── EnrollmentService.java
│ └── CourseService.java
├── repository/
│ └── StudentRepository.java
└── util/
├── EmailSender.java
└── DateUtils.java
See on levinud suuremates rakendustes ja raamistikes (näiteks Spring soodustab seda mustrit).
Miinus on see, et ühe funktsionaalsuse mõistmiseks tuleb liikuda mitme paketi vahel:
- mudel (kuidas kirjeldada näiteks tudengit süsteemis) asub
model/pakis - äriloogika
service/pakis - andmesalvestus
repository/pakis
Väiksemates projektides ja õppetöö puhul on funktsioonipõhine organiseerimine enamasti selgem.
Sõltuvuste suund ja circular dependency
Hea praktika on hoida sõltuvused selge suunaga. Kõrgema taseme loogika võib sõltuda madalama taseme komponentidest, kuid vastupidi mitte.
Selle mitte järgimisel tekib circular dependency nähtus ehk kus sõltuvused on omavahel sõltuvuses (nagu lõputu tsükkel). A sõltub B-st, B sõltub A-st. Seda tuleks vältida, sest see muudab süsteemi raskesti mõistetavaks ja hooldatavaks.
Nähtavus ja kapseldamine
Kõik klassid ei pea ilmtingimata olema public nähtavusega.
Java võimaldab kasutada ka package-private nähtavust ehk klass on nähtav ainult klassidele, mis asuvad samas paketis.
See aitab:
- piirata sisemiste detailide lekkimist
- hoida paketi sees kindlaid piire
- vähendab juhulikku sõltuvuste teket
Pakk ei ole ainult kaust, see on ka üks kapseldamise mehhanismidest.
Testide struktuur
Lisaks kui projektis on kasutusel ehitusinstrument (build tool, näiteks Maven või Gradle), siis need võimaldavad projekti rohkem moduliseerida. See tuleb kasuks automaattestide puhul. Testid ja kood peaksid reeglina eraldatud olema, näiteks Maven või Gradle projektides oleks struktuur järgmine:
src/
├── main/
│ └── java/
│ └── university/...
└── test/
└── java/
└── university/...
Testid peaksid peegeldama põhikoodi struktuuri.
Kui klass asub university.students pakis, siis ka vastav testklass peaks asuma samas pakis testikaustas.
Nimereeglid
Järjepidev nimetamine teeb koodibaasis navigeerimise oluliselt lihtsamaks.
Klassid
| Klassi tüüp | Konventsioon | Näited |
|---|---|---|
| Tavaline klass | UpperCamelCase | Student, BankAccount, EmailSender |
| Liides | UpperCamelCase | Printable, Comparable |
| Abstraktne klass | UpperCamelCase | AbstractVehicle, BaseRepository |
| Testklass | <ClassName>Test | StudentTest, CourseServiceTest |
Pakid
| Paki tüüp | Konventsioon | Näited |
|---|---|---|
| Tavalised pakid | lowercase, punktiga eraldatud | university.students, util |
| Funktsionaalsuse järgi eraldatud pakid | nimisõna, mis kirjeldab funktsionaalsust | students, courses, payments |
| Utiilpakid | util or common | util, common |
Meetodid ja muutujad
| Tüüp | Konventsioon | Näited |
|---|---|---|
| Meetodid | lowerCamelCase | getBalance(), calculateTax() |
| Muutujad | lowerCamelCase | studentName, accountBalance |
| Konstandid | UPPER_SNAKE_CASE | MAX_ATTEMPTS, DEFAULT_TIMEOUT |
Moodulid
Moodulid (Java Platform Module System)
Alates Java 9-st toetab Java mooduleid (JPMS – Java Platform Module System). Kui pakid organiseerivad klasse loogilisteks gruppideks, siis moodulid organiseerivad terveid pakkide gruppe ja määravad, millised neist on teistele nähtavad. Kui pakid aitavad korrastada projekti sees olevat koodi, siis moodulid aitavad korrastada suuremaid süsteeme või mitmest alamprojektist koosnevaid rakendusi.
Näiteks projekt võib olla jagatud kaheks mooduliks:
university.core/
├── module-info.java
└── university/
├── students/
└── courses/
university.app/
├── module-info.java
└── university/app/
university.coreteeb oma paketid nähtavaksuniversity.appdeklareerib, et ta sõltubuniversity.coremoodulist
Moodulid aitavad:
- Selgelt määratleda süsteemi osade vahelised sõltuvused
- Piirata nähtavust suuremas skaalas (mitte ainult paketi tasemel)
- Vältida soovimatuid sõltuvusi
- Parandada hooldatavust suurtes projektides
IntelliJ IDEA moodulid
IntelliJ IDEA-l on oma moodulite mõiste, mis ei ole sama Java moodulitega (module-info.java), kuid tihti kattub või toetab neid.
IntelliJ moodul on projekti loogiline osa, millel on oma seaded, sõltuvused ja kompileerimisrežiim.
Moodul võib sisaldada mitut paketti ja faile (src/), samuti sõltuda teistest IntelliJ moodulitest.
Moodulid aitavad hajutada suuri projekte ja hallata sõltuvusi IntelliJ-siseselt, näiteks kui sama projekt sisaldab kliendi-, serveri- ja jagatud koodi.
Näiteks:
university-core (IntelliJ module)
├── src/
└── main/java/university/students
└── main/java/university/courses
university-app (IntelliJ module)
├── src/
└── main/java/university/app
university-test (IntelliJ module)
├── src/
└── test/java/university/
university-appsõltubuniversity-coremoodulist IntelliJ tasemel- See sõltuvus võimaldab kompileerida ja jooksutada mooduleid eraldi, ilma et kõik oleks ühes suurtes
srckaustades
IDEA moodulid aitavad:
- Hallata suuri projekte mugavalt IDE-s
- Hoida sõltuvused selged ja kompileerimise eraldatud
- Lihtsustada testide ja erinevate komponendi konfiguratsioonide eraldamist
IDEA moodulid vs JPMS
| Mõiste | Tasand | Selgitus |
|---|---|---|
| IntelliJ moodul | IDE/projekti tasand | Jagab projekti loogilisteks osadeks IntelliJ-s; määrab sõltuvused ja kompileerimise reeglid |
| Java moodul | Kooditasand | Deklareeritud module-info.java; kontrollib nähtavust ja sõltuvusi Java kompilaatori ja JVM tasemel |
IntelliJ mooduleid saab kasutada ka Java moodulitega koos: IntelliJ moodul võib sisaldada ühe või mitu Java moodulit.
Praktiline näide
Oletame, et arendad mängu või ärirakendust, kus nii frontend (klient) kui ka backend (server) kasutavad Javat. Paljud klassid on jagatud nende kahe vahel, näiteks mängulogiika, andmemudelid ja abifunktsioonid.
Kui teha kaks eraldi projekti (Client, Server), tekib palju duplikeerimist.
Antud juhul oleks mõistlik neid näiteks hoida ühes projektis kolme moodulina:
GameProject (IntelliJ Project)
├── Core (module)
│ └── src/main/java/... // shared logic and models
├── Server (module)
│ └── src/main/java/... // server specific code
│ └── depends on Core module
├── Client (module)
└── src/main/java/... // client specific code
└── depends on Core module
Coresisaldab kogu jagatud koodi, näiteks mängureeglid, utiliidid ja andmemudelidServerjaClientsõltuvadCoremoodulist, kuid ei dubleeri ühtegi klassi- IntelliJ hallab sõltuvusi, kompileerimist ja jooksutamist mugavalt ühe projekti sees
See lähenemine vähendab koodi duplikaati, parandab hooldatavust ja võimaldab eraldi arendada frontend- ja backend-moodulit, säilitades siiski ühtse projekti struktuuri.