S obzirom da se često radi u timovima, a i postoje „legacy“ projekti koje je potrebno održavati, svaki programer bi trebao težiti tome da njegov kod bude čitljiv i razumljiv drugima. Ne postoji točno određeno značenje „dobrog/kvalitetnog koda“, ali zato postoje neka pravila u pisanju koda kojih se valja pridržavati.
Kako bi kod bio čitljiviji i shvatljiviji, korisno je smanjiti broj varijabli koliko god je to moguće. Prije, u starijim programskim jezicima, npr. C, učili su nas deklarirati sve varijable na početku klase. Java dopušta deklariranje varijabli bilo gdje i time nam daje veliku slobodu.
Jedna od tehnika smanjivanja broja varijabli je što više koristiti lokalne varijable. Ako deklariramo neku varijablu prije nego što ju iskoristimo, njen „scope“ će početi prerano, ali i završiti prekasno čime možemo zagušiti program. Također, ako je varijabla deklarirana prije bloka u kojem ćemo ju koristiti, ostaje vidljiva i nakon izvršavanja bloka – kasnije ju možemo slučajno iskoristiti u nekom drugom dijelu koda i time dovesti aplikaciju do nestabilnosti i mogućeg raspadanja.
Korištenje petlji jedna je od najučestalijih stvari u programiranju – smanjuju broj varijabli, čine kod čitljivim te smanjuju količinu koda – zato je bitno koristiti ih pametno. Ako i dalje uzmemo u obzir tendendciju korištenja lokalnih varijabli, „for“ petlja je pogodnija od „while“ petlje – manja je mogućnost pogreške kod kopiranja koda (ako se kopiraju dvije „for“ petlje, neće se dogoditi nikakva greška s obzirom da su petlje potpuno neovisne jedna o drugoj). Na kraju, „for“ petlja je kraća i jasnija za pročitati.
Korisno je izdvajati logiku u posebne metode. Ako te metode opet postanu dugačke, potrebno je izdvojiti ih u manje cjeline. Cilj je održati metode malim i fokusiranim na određenu cjelinu.
Kad u pitanje dođe rasprava o „for“ petlji i „for-each“ petlji, trebalo bi uzeti u obzir da se s „for-each“ petljom ne moramo brinuti o indeksiranju (a i zašto brinuti o indeksima – ako su nam potrebni samo elementi?) te je manja mogućnost pogreške (npr. ako želimo dohvatiti element koji ne postoji na poziciji 4). Ipak, u nekim situacijama se ne može koristiti “for-each” petlja, kao što su: destruktivno filtriranje (npr. brisanje određenog elementa u listi), izmjena elemenata (npr. izmijeniti određeni element u listi) i paralelna iteracija.
Jedna od korisnijih stvari su i mnogi pristupačni Java “libraryji”; zašto gubiti vrijeme na nešto što je već implementirano, testirano i provjereno? Obzirom da postoji veliki „community“ koji svakodnevno koristi “libraryje” te uočava nedostatke i objavljuje ih razvojnom timu, nepotrebno je pokušavati razviti vlastito rješenje (osim ako „library“ ne pokriva ono što želimo postići). Također, “libraryji” se svakodnevno poboljšavaju bez ikakvog našeg truda te nam omogućavaju lakšu čitljivost koda, kasnije lakše održavanje, ali i ponovno korištenje zato što su jasni većini programera. Ako i nisu, svi “ibraryji” su jako dobro dokumentirani te se može bez problema doći do dokumentacije.
Tipovi podataka „float“ i „double“ su primarno stvoreni za znanstvene i inženjerske kalkulacije. Ako nam trebaju neke egzaktne vrijednosti, bolje ih je ne koristiti. Umjesto njih, možemo upotrijebiti „BigDecimal“, „int“ ili „long“ – pogotovo za računice s novcima. „BigDecimal“ je malo kompleksnije koristiti i ima lošije performanse, ali nam daje potpunu kontrolu oko „zaokruživanja“ brojeva – omogućujući nam da izaberemo između osam načina „zaokruživanja“ kad god se obavlja takva operacija.
Ako možete birati između primitivnih tipova („int“, „double“, „boolean“) i „box“-anih („Integer“, „Double“, „Boolean“), prije izaberite primitivne: primitivni tipovi zauzimaju manje prostora i imaju bolje performanse, ne sadrže ništa drugo osim točne vrijednosti (npr. dvije instance „Integer“ tipa imaju jednaku vrijednost, ali različit „ID“ što bi moglo dovesti do greške kod uspoređivanja podataka), ne sadrže „null“,…
Primjer: “”
Comparator<Integer> order = (i, j) -> (i < j) ? – 1 : (i == j ? 0 : 1);
Za:
order.compare(new Integer(42), new Integer(42));
Ispisat će 1, makar su vrijednosti iste. == se ne smije koristiti kod uspoređivanja dva „box“-ana tipa jer uspoređuje po identitetu.
“Stringovi” su dizajnirani za spremanje nekog teksta ili niza slova. Obzirom da su “Stringovi” toliko učestali i jako dobro podržani, težimo ih često koristiti – čak i tamo gdje nije prikladno. Loša su zamjena za druge vrste tipova podataka; npr. ako je neka vrijednost u formi numerička, trebalo bi ju spremiti u „int“, „float“ ili „BigDecimal“; tojest, ako postoji neki prikladniji tip ili objekt za vrijednost, trebalo bi ga iskoristiti; a ako ne postoji, bolje je napisati vlastiti nego neprikladno koristiti „String“. Isto tako, loša su zamjena i za enumeracije i agregate. Ako se ne koriste ispravno, često su nezgrapni, sporiji, nefleksibilni i skloniji pogrešci.
Kod priče o “Stringovima”, treba obratiti pozornost i na konkatenaciju. Konkatenacija je, inače, pogodan način za spajanje i kombiniranje više “Stringova” u jedan. Ali ako se često koristi – npr. konkatenacija n “Stringova”, zahtjeva vrijeme = n2 – može doći do sporosti aplikacije zbog toga što su “Stringovi” nepromjenjivi i zapravo se kopira sadržaj “Stringova”. Pametnije je koristiti „StringBuilder“ koji je puno brži.
Referencirajte objekte preko njihovih „interfacea“; ako se naučite tako pisati, vaš kod će biti puno fleksibilniji.
Primjer:
LinkedHashSet<Book> bookSet = new LinkedHashSet<>();
Set<Book> bookSet = new LinkedHashSet<>();
Prva linija u primjeru pokazuje neispravno deklariranje seta, dok druga linija ispravno. Ovakav način je i brži – u slučaju da „LinkedHashSet“ želimo promijeniti u „HashSet“, samo mu izmijenimo naziv klase u konstruktoru i sav ostali kod će i dalje raditi (samo treba paziti kod ove konverzije u slučaju da je poredak elemenata bitan). Naravno, postoje slučajevi kad se ne mogu koristiti “interfacei”; npr. klase poput „String“ i „BigDecimal“, takozvane „Value Classes“ – često su finalne i rijetko imaju korespondirajući „interface“. Još jedan slučaj je ako objekt pripada nekom „framework“-u kojem su temeljni tipovi klase (koje su često apstraktne), a ne “interfacei” – npr. java.io.OutputStream. Ako ne postoji odgovarajući „interface“, koristite najodređeniju klasu u hijerarhiji koja odgovara vašim potrebama i pokriva traženu funkcionalnost.
Refleksija je jedna od mnogih značajki Jave. Preko refleksije možemo pozvati metode u “runtimeu”; to je sposobnost modificiranja koda u „runtimeu“ koristeći introspekciju (automatski proces analize klasa kako bi se otkrila njihova svojstva i metode) te pristupati proizvoljnim klasama.
Primjer:
( – pretpostavka da postoji objekt nepoznatog tipa – foo,
– „null“ u prvoj liniji označava da ne postoje parametri koji se predaju u metodu)
Method method = foo.getClass().getMethod(„doSomething“, null);
method.invoke(foo, null);
Makar je korisno i snažno svojstvo koje je potrebno za određene zadatke za programiranje sustava, ne treba biti korištena svakodnevno. Postoje razni razlozi zašto – a neki su: gube se sve prednosti provjere u „compile-timeu” (uključujući i provjere „exception“-a; npr. ako program pokuša dohvatiti nepostojeću metodu reflektivno, „raspast“ će se tek na „runtime“-u), kod koji koristi refleksiju je nezgrapan i preopširan, naporno ga je pisati, teško čitati te su jako loše performanse takvog programa (poziv metode preko refleksije je puno sporiji od normalnog poziva metode).
„Java Native Interface“ (JNI) omogućava Java programima korištenje „nativnih“ (izvornih) metoda – to su metode koje su napisane u nekom drugom programskom jeziku, kao što je C, C++,… Korištene su iz tri razloga: pružaju pristup sadržajima specifičnih za platformu, poput „registryja“, pružaju pristup postojećim „library“-ima izvornog koda (uključujući „legacy libraries“ koje omogućavaju pristup „legacy“ kodu) te su korištene kako bi se poboljšale performanse u kritičnim dijelovima programa. Kako je Java kao platforma rasla i razvijala se, tako su razvijane i mnoge značajke koje su prije bile dostupne samo preko „host“ platformi. Danas se rijetko kad preporuča koristiti izvorne metode – nisu sigurne i aplikacije koje ih koriste nisu imune na korupciju memorije. Također, obzirom da su izvorni jezici ovisniji o platformi, to programe čini ne prijenosnima, teže ih je „debuggati“ te, ako niste oprezni, korištenje takvih metoda može usporiti aplikaciju jer „garbage collector“ ne može pratiti njihovu upotrebu.
Kad se priča o optimizaciji koda, često nailazimo na suprotne stavove; neki smatraju da je najbolje kod optimizirati odmah tijekom razvoja dok drugi suprotno, tj. kad se ukaže potreba. Svaka strana ima svoje prednosti i mane, no bitno je sljedeće:
– težite pisanju dobrih i kvalitetnih aplikacija, a ne brzih
– ne žrtvujte principe arhitekture aplikacije zbog performansi
– nastojte izbjeći odluke o dizajnu aplikacije koje ograničavaju performanse
– razmotrite posljedice izvedbi vaših odluka o dizajnu API-ja
– loša ideja je „iskriviti“/“izmijeniti“ API kako bi postigli bolje performanse
To ne znači da ne morate brinuti o performansama aplikacije nego da trebate razborito optimizirati jer optimizacija često napravi više štete nego koristi, pogotovo kad se napravi prerano. Pametno je izmjeriti performanse prije i nakon svake optimizacije jer se ponekad zna dogoditi da program bude i sporiji zato što je zapravo teško pogoditi gdje program troši najviše vremena (npr. mislite da neki dio koda uzrokuje probleme i provedete dosta vremena optimizirajući ga da bi na kraju shvatili da nema razlike u performansama ili je aplikacija još i sporija zato što je uzrok na nekom drugom mjestu). Korisno je koristiti alate za profiliranje koji vam daju informacije koliko neka metoda troši vremena i koliko puta je pozvana. Također, postoji i JMH – „framework“ koji omogućava detaljnu analizu performansi Java koda.
„We follow two rules in the matter of optimization:
Rule 1: Don’t do it.
Rule 2 (for experts only): Don’t do it yet – that is, not until you have a perfectly clear and unoptimized solution.“
M. A. Jackson
Java sadrži puno „naming“ konvencija kojih se valja pridržavati. Većina ih je sadržana u „The Java Language Specification“ (JLS). Trebali bi ih rijetko kršiti – i to samo ako postoji jako dobar razlog. Ako se ne pridržavate konvencija, program može biti teško održiv, a i može zbuniti druge programere. Mogu se podijeliti u dvije kategorije, a to su: tipografske i gramatičke. Tipografske se odnose na načine kako pisati npr. pakete, klase, metode i varijable. Neka od tipografskih pravila su:
– paketi i moduli moraju biti poredani hijerarhijski, s komponentama odvojenim točkom; komponente treba pisati malim slovima; paketi započinju s internetskom domenom organizacije – npr. com.google, edu.cmu,…
– klase i „interface“-i, uključujući enumeracije, se trebaju sastojati od jedne ili više riječi s prvim velikim slovom svake riječi – npr. List, GameActivity,…
– metode se trebaju sastojati od jedne ili više riječi; trebaju započinjati malim slovom, a svaka ostala riječ treba početi velikim slovom – npr. seeResults, remove, groupingBy,…
– lokalne varijable – slično pravilo kao za metode, samo što su dopuštene kratice – npr. houseNum, maxCapacity,…
– konstante bi se trebale sastojati od jedne ili više riječi, sva velika slova; između svake riječi treba biti podvlaka (i samo u konstantama se smije koristiti) – npr. DELAY_MILLIS, MAX_VALUE,…
Gramatička pravila su fleksibilnija i kontroverznija od tipografskih. Neka od usvojenih pravila su:
– klase, uključujući enumeracije, se obično nazivaju imenicom – npr. Collectors, Collections,…
– metode koje izvršavaju neku akciju, deklariraju se s glagolom ili glagoljskim izrazom – npr. append, drawImage,…
– metode koje vraćaju „boolean“ tip podataka obično započinju s riječi „is“/“has“ i nakon toga slijedi imenica, imenički izraz ili bilo koja riječi koja je u ulozi pridjeva – npr. isEmpty, isEnabled,…
– metode koje vraćaju ostale tipove podataka obično se nazivaju s imenicom, imeničkim izrazom ili glagoljskom frazom započinjući ju s glagolom „get“ – npr. getTime, hashCode,…
Naučite koristiti „naming“ konvencije u svakodnevnom životu jer će vama i programerima oko vas olakšati život i standardizirati kod koji pišete. Tipografske konvencije su jasne i nedvosmislene, dok su gramatičke ipak malo kompleksnije.
Kao što piše u „The Java Language Specification“:
„These conventions should not be followed slavishly if long-held conventional usage dictates otherwise.”
Budite razumni i pametni pri pisanju koda. Držeći se najboljih programskih praksi i savjeta, možete poboljšati razvoj aplikacija, kvalitetu koda i izbjeći neke uobičajene greške i zamke.