Blog
Skip to end of metadata
Go to start of metadata

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? (smile)

      
      
Page viewed times

9 Comments

  1. 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. ;-) )

  2. Mai kedvencem:

    try {
      // ...
    } catch(Exception ex) {
      logger.severe(ex.toString());
    }

    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.

  3. 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 :)

  4. Unknown User ((k)risztián)

    3-asra példa pl: egy szálban statikus tulajdonság inkrementálását végzed kiíratással megadott szám-szor.... 1 szál esetében szépen nőnek majd a kiirt értékek.... több szál esetében pedig hát néha átvált a kiíratás és a növekvés között... így többel nő vagy nem nő....
  5. A 6-os pontban a magyarázat ("a primitív típusok (és azok befoglaló osztályai) érték szerint adódnak át, minden más referencia szerint") nem teljesen igaz, az alábbi olvasnivalót javaslom hozzá: www.yoda.arachsys.com/java/passing.html
  6. 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.

    1. Üres catch ágnál legalább egy comment-et illik beírni, pl. "never happens".

  7. Anonymous

    A következő kijelentések részben tévesek:

     

     "...a primitív típusok (és azok befoglaló osztályai) érték szerint adódnak át, minden más referencia szerint..."

    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:

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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.

     

     

     "...a Java minden paramétert referencia szerint ad át, kivéve a primitív típusokat..."

    Auth Gábor

    Ööö... izé... a Java minden paramétert referencia szerint ad át, kivéve a primitív típusokat...

    Létre tudod hozni a gombcsoportot, hogy például listát adsz vissza és általában egy eseménykezelő van, amely minden esemény belépő pontja, vagy inner anonymous class-ként létre tudsz hozni külön-külön eseménykezelő példányokat is.

    Megvan a logikája és működésmódja a Java-nak, ha úgy fejlesztesz benne, ahogy kell, akkor keveset kell kódolni... persze a régi mondás igaz lehet: egy Fortran programozó minden nyelven képes Fortran programot írni... (smile)

     

    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.

    1. Auth Gábor AUTHOR

      Azt mondanám, hogy az objektum referenciát érték szerint adja át. (smile)