Liigu peamise sisu juurde

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üüpKonventsioonNäited
Tavaline klassUpperCamelCaseStudent, BankAccount, EmailSender
LiidesUpperCamelCasePrintable, Comparable
Abstraktne klassUpperCamelCaseAbstractVehicle, BaseRepository
Testklass<ClassName>TestStudentTest, CourseServiceTest

Pakid

Paki tüüpKonventsioonNäited
Tavalised pakidlowercase, punktiga eraldatuduniversity.students, util
Funktsionaalsuse järgi eraldatud pakidnimisõna, mis kirjeldab funktsionaalsuststudents, courses, payments
Utiilpakidutil or commonutil, common

Meetodid ja muutujad

TüüpKonventsioonNäited
MeetodidlowerCamelCasegetBalance(), calculateTax()
MuutujadlowerCamelCasestudentName, accountBalance
KonstandidUPPER_SNAKE_CASEMAX_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.core teeb oma paketid nähtavaks
  • university.app deklareerib, et ta sõltub university.core moodulist

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-app sõltub university-core moodulist IntelliJ tasemel
  • See sõltuvus võimaldab kompileerida ja jooksutada mooduleid eraldi, ilma et kõik oleks ühes suurtes src kaustades

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õisteTasandSelgitus
IntelliJ moodulIDE/projekti tasandJagab projekti loogilisteks osadeks IntelliJ-s; määrab sõltuvused ja kompileerimise reeglid
Java moodulKooditasandDeklareeritud 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
  • Core sisaldab kogu jagatud koodi, näiteks mängureeglid, utiliidid ja andmemudelid
  • Server ja Client sõltuvad Core moodulist, 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.