Dolgozz keveset, nem ér baleset - tartja a mondás, s igaz ez a Java programozókra is, hiszen ha dolgozunk, közben hibákat is vétünk, de nem mindegy milyen hibákat. David Reilly csokorba szedte a leggyakoribb programozói hibákat, lássuk a Top10 listát.
10. Nem statikus tagot akarunk elérni statikus metódusból
A Java programozásban eleinte furcsa lehet a statikus és a nem statikus (példányosított) változók és metódusok megkülönböztetése. A statikusnak jelölt részek nem igényelnek példányosítást, az összes többi helyen a deklarált változók csak példányosítás után érhetők el, vagyis belőlük tetszőleges számú példány létezhet. Mivel a programozási példák erősen építenek a statikus main metódusra, a kezdőket összezavarhatja, hogy mit lehet elérni a main metódusból és mit nem, az alábbi kód nem fog fordulni, hiszen a valami nevű változó nem statikus változó:
public class StaticDemo { public String valami = "valami cucc"; public static void main (String args[]) { System.out.println ("Írjunk ki a valami tartalmát: " + valami ); } }
9. Öröklődésnél elírjuk a felüldefiniálandó metódus nevét
Az öröklődés az objektum orientált programozás lételeme, mondhatni áldás, de átok is egyben, ha a felüldefiniálandó metódus nevét elírjuk akár egy betűvel is. Ez utóbbi esetben ugyanis a leszármazott osztályban nem a látszólag meghívott metódus fog lefutni, hanem az ősosztály megfelelő nevű metódusa. A hibát éveken keresztül programozók ezrei ejtették mondhatni napi rutinnal, de az ötös verzió óta az @Override annotációval végre kivédhetünk, hiszen ezzel az annotációval jelezzük a fordító felé, hogy valamelyik ős metódusát felül szeretnénk definiálni, s ha nincs egyik osztályban se ilyen, akkor a kód nem fog fordulni.
8. Összehasonlítás = jellel
Sok egyéb nyelvben az összehasonlítás az egyenlőség jellel történik - ahogy azt a matematika is teszi - viszont sok programozási nyelv esetén az egyenlőség jel a legyen egyenlő utasítást takarja. A C nyelvhez sokkal jobban hasonlító nyelvek különösebb lelkifurdalás nélkül elfogadják egy feltételben az értékadást és az eredményből igaz/hamis értéket képezve döntik el, hogy a program végrehajtása melyik ágon folytatódjon. Szerencsére a Java ezt nem teszi lehetővé, és az erősen típusosság is kivédi a hibalehetőségeket, így ezt a hibát csak a zöldfülű kezdők vétik a Java karrierjük első pár napján.
7. Példányok összehasonlítása == jellel
A Java nyelven kívül szinte minden egyéb nyelvben két akármi között egyenlőséget tudunk vonni, és az igaz/hamis eredményt fel tudjuk használni. Java bármely két példány között elfogadott az == jel, de nem a példány értékét hasonlítja össze, hanem a referenciát - vagyis hogy a két példány valóban egy és azonos-e. Az alábbi programrészlet szerint mind a két változónak az értéke "23" lesz, mégis két külön példány a memóriában, ezért példány szerint (==) nem lesznek egyenlők, de érték szerint (.equals) igen.
String a="23"; String b="2"; b=b+"3"; System.out.println(a==b); System.out.println(a.equals(b));
6. Érték szerint vagy referencia szerint?
A Java nyelv elrejti a memóriát a programozó elől, a szemétgyűjtő szálra hagyva a memória tisztán tartását. Ha változókat használunk, tudnunk kell mit jelent a referencia szerinti és érték szerinti átadás, hiszen ez nagymértékben befolyásolja a programjaink hibátlan működését: a primitív típusok (és azok befoglaló osztályai) érték szerint adódnak át, minden más referencia szerint. Ez azt jelenti, hogy ha átadunk egy vektort egy metódusnak, akkor a metódusok belül hozzá tudunk adni új elemet, vagy törölni tudjuk annak tartalmát:
public static void add(Vector vector) { vector.add("0"); } public static void create(Vector vector) { vector = new Vector(); } public static void main(String[] args) { Vector vector=new Vector(); add(vector); System.out.println(vector.toString()); create(vector); System.out.println(vector.toString()); }
A várt eredmény, hogy a második kiírásnál üres vektort kapunk - elmarad, mivel a create metódusban nem a referencián végzünk műveletet, hanem új értéket adunk neki, ezáltal a metódusok belül minden művelet egy új vektor példányra fog végrehajtódni, egyik művelet sem befolyásolja a paraméterben kapott változót. Gyakori hiba még haladóbb programozók között is.
5. Üresen hagyott catch ág
A struktúrált programozás leghasznosabb találmánya a try-catch blokk, amely igen hatékony hibakezelést tesz lehetővé. Sokszor találkozni olyan try-catch programrészekkel, amelyben a catch ágban nem történik semmi:
try { // ... } catch (Exception except) { }
Bármilyen hiba is keletkezik a try blokkon belül, a catch ág elkapja, majd jól nem csinál semmit, a program fut tovább, a programozó pedig a felhasználóra fogja a problémát, hiszen nincs semmi hiba se a konzolon, se a naplóban...
4. Minden index nullával kezdődik...
Sok nyelvben a halmazok vagy listák első eleme az első (1) indexet viseli, sok másik nyelvben pedig a nulladik (0) indexet; a Java ez utóbbi nyelvek közé tartozik. Szerencsére túlcímzés esetén futás közben kivételt kapunk, hogy a tömb nem tartalmaz annyi elemet, ahányadikat használni szeretnénk. Persze ha üres a catch blokk... akkor nem kapunk kivételt... :)
Ha végre megszokjuk a nullával kezdődő indexelést, akkor a JDBC ezt a tudást porig rombolja, ugyanis itt minden sorszám egyel kezdődik... a dátumok esetén pedig - teljesen logikusan - a hónapok nullával kezdődnek, a január a 0. hónap, a napok viszont egyessel kezdődnek.
3. Több szálon futás
Még több éves tapasztalatok után is képes az ember hibákat hagyni egy több szálon futó programban, amikor szinkronizálás nélkül használ egy-egy változót - amely általában nem szálbiztos. Persze tesztelni egy szálon tesztel, és a kész rendszer a terhelés növekedtével egyre több rejtélyes hibát okoz...
2. Kisbetű-nagybetű probléma
Jó pár friss Java programozóknak okoz gondot a Java nyelv metódus és változó elnevezési konvenciója. Minden változót kisbetűvel írunk - az első szót követően minden szó első betűje nagybetű, ellenben minden konstanst nagybetűvel deklarálunk. Ehhez jön még a getter/setter minta, amikor a változók nevéhez ragasztjuk a get/set szót és ilyen néven szerepeltetjük ezeket a metódusokat. A kezdő programozók pár osztály után, az önfejű programozók sok-sok osztály után döbbennek rá, hogy ezen az elnevezési módszeren egy csomó Java technológia alapul...
1. A null pointer
Minden hibák legalattomosabbika, még a legtapasztaltabb öreg rókák életét is megkeseríti, ha egy változót nem példányosít, majd megpróbálja a példányváltozókat használni. Természetesen nem lehet a nyelvből kivenni a null értéket, hiszen egy csomó metódus ad vissza null értéket - akár üzemszerűen, akár hibát jelezve: sajnos ezzel együtt kell élni. Mint minden nyelvben - a Java nyelvben is vannak érdekes mellékhatások, íme a legrövidebb Java program, amely egyszerűen csak hibásan működik, pedig hiba nélkül lefordítható:
public class Main { public static void main(String[] args) { throw null; } }
Vélemény?
9 Comments
Bakos Gábor
A 8-as egy esetben előfordulhat Java esetén is. Amennyiben a változó típusa boolean teljesen valid az if (a=true) kifejezés. (Eclipse egyik beállítási lehetősége ehhez figyelmeztetést, vagy hibát rendelhet.)
5-ösnél egyértelműen el kell törni a delikvens kezét. (Elismerem lehet létjogosultsága, de akkor írjon oda megjegyzést, hogy miért tette ezt. Én például ha DataInputStream-ből beolvasom a strukturákat amiket kell még szoktam try-catch-csel egy újabb beolvasást végezni, hogy biztos legyek benne, hogy valóban mindent elolvastam, valid az állomány. Természetesen a beolvasás után egy assert false szokott állni, így: try {dis.readByte();assert false; } catch(IOException e) {/*Expected control flow.*/})
A 3-ashoz tud valaki normális teszteket készíteni? Láttam ugyan a MultithreadedTestCase-t, de még nem használtam. (Ami azt illeti néha nem ártott volna, de idő nem volt...)
Az 1-est egész szépen tudja jelezni a FindBugs. Már csak egy JSR 305-nek megfelelően annotált JDK kellene. (És persze még amiket szoktam használni. ;-) )
Kucsora Róbert
Mai kedvencem:
Informatívabb, mint az üres catch törzs, de nem sokkal. Idén újévkor pedig ez a január = 0. hónap dolog viccelt meg egy kicsit. Ez tényleg idegesítő tud lenni, a mai napig nem értem ebben a fantáziát.
Csányi András
Nekem - kezdő harcosnak, kimondottan jól jönnek az ilyen leírások.
Az 5 -re nekem is eszembe jutott egy eset. Tisztelt kollega, multinaci alkalmazottja jó üresen hagyta a catch ágat és a mi kollegánk jó fél napig kereste, hogy mi a fene történik. Ott is elhangzottak szép dolgok :)
Unknown User ((k)risztián)
solutor.
Garami Gábor
Egy eset van, amikor talan lehet valami ertelme az ures catch agnak: ha tudjuk, hogy ez problema lesz, viszont ez uzemszeru. Nalam az UIManager kezelese soran fordult elo, hogy a default betoltese utan uresen hagytam: elmeletileg ki kell tolteni, mert maga a setLookAndFeel valoban dob egy exception-t ha baj van, viszont eleg nagy bajnak kell lenni, ha a default lookandfeel-t sem tudja betolteni a rendszer - altalaban ilyenkor mar elobb elszall az egesz, hiszen ezek az osztalyok a rt.jar-on belul vannak.
tvik
Üres catch ágnál legalább egy comment-et illik beírni, pl. "never happens".
Anonymous
A következő kijelentések részben tévesek:
A helyzet az, hogy a Java-ban kizárólag érték szerinti paraméterátadás létezik. A JLS 4.12.3 fejezetének 4. és 5. pontjában leírtak értelmében ugyanis a metódusok meghívásakor minden paraméter számára egy-egy új lokális változó jön létre. Ebből következik, hogy amikor értéket adunk egy metódus paraméterének (ami egyébként dizájn-megfontolások miatt a gyakorlatban kerülendő), ezen az egy változón kívül semmilyen más változó értéke nem változik meg.
A félreértést általában az okozza, hogy gyakran össze szokták keverni azt a két kérdést, hogy egy metódus módosíthatja-e a paraméterének átadott változó értékét, illetve azt, hogy egy metódus módosíthatja-e a paraméterének átadott változó értéke által hivatkozott objektum állapotát? Az első kérdésre a válasz határozott NEM, a másodikra pedig IGEN, ha az adott objektum ezt megengedi. A példa egyébként mindkét esetet jól szemlélteti.
Auth Gábor AUTHOR
Azt mondanám, hogy az objektum referenciát érték szerint adja át.