A Megrendelés nyilvántart szoftverem utolsó finomításait végzem, most foglalkoztam azzal, hogy bolondbiztossá tegyem. Ennek az lenne a célja, hogy akármilyen adatokat is adjon be a felhasználó, a szoftver ne álljon le hibajelzéssel, hanem kapjam el a kivételeket vagy előzzem meg a hibajelzések adását.
Ebben a bejegyzésben ezeken a hibakezelési dolgokon megyek végig, és ahol szükséges, ott forráskóddal mutatom meg, miről van szó.
Megrendelések felvitele
Egy korábbi bejegyzésemnél már említettem a megrendelések felvitele és módosítása ablakot, ahol az író munkabérét és a nyereséget valós időben változtattam, a mennyiség, az egységár és az író egységára mezők alapján. Erről volt szó:
Ez a metódust viselt gondot az író fizetésének frissítéséről:
private void fillSallaryAmount() { double amount = Double.valueOf(quantityField.getText()) * Double.valueOf(rateField.getText()); sallaryAmountLabel.setText(String.valueOf(amount)); }
Ez pedig a nyereségről:
private void fillProfitAmount() { double amount = Double.valueOf(quantityField.getText()) * Double.valueOf(unitPriceField.getText()) - Double.valueOf(quantityField.getText()) * Double.valueOf(rateField.getText()) ; profitAmountLabel.setText(String.valueOf(amount)); }
A múltkori bejegyzésben már volt róla szó, hogy milyen Listenerrel oldottam meg a mezők figyelését. Akkor azt is elmondtam, hogy ha rossz számot kezdtem el beírni, és teljesen visszatöröltem az értéket, akkor a figyelő elkapta azt a pillanatot is, amikor épp üres volt a mező, és erre ezt a hibát dobta:
Exception in thread "AWT-EventQueue-0" java.lang.NumberFormatException: empty String
A nem számok beírásakor pedig bár én a dialógusablakban nem láttam, a háttérben a Java már hibát dobott, mivel a beírt nem szám karaktereket a Listener továbbította a két fenti metódusnak. A Double.valueOf nem szám értékre pedig ezt eredményezi:
Exception in thread "AWT-EventQueue-0" java.lang.NumberFormatException: For input string: "s"
A valós időben figyelő Listener miatt nem elég csak a Küldés gomb megnyomásakor ellenőrizni a beviteli mezőket, így a fenti két metódust átírtam:
private void fillSallaryAmount() { try { if (quantityField.getText() != null && !quantityField.getText().isEmpty() && rateField.getText() != null && !rateField.getText().isEmpty()) { double amount = Double.valueOf(quantityField.getText()) * Double.valueOf(rateField.getText()); sallaryAmountLabel.setText(String.valueOf(amount)); } else { sallaryAmountLabel.setText(""); } } catch (NumberFormatException ex) { // nothing happens, just handling if a user is pressing a non-number key } } private void fillProfitAmount() { try { if (quantityField.getText() != null && !quantityField.getText().isEmpty() && unitPriceField.getText() != null && !unitPriceField.getText().isEmpty() && rateField.getText() != null && !rateField.getText().isEmpty()) { double amount = Double.valueOf(quantityField.getText()) * Double.valueOf(unitPriceField.getText()) - Double.valueOf(quantityField.getText()) * Double.valueOf(rateField.getText()) ; profitAmountLabel.setText(String.valueOf(amount)); } else { profitAmountLabel.setText(""); } } catch (NumberFormatException ex) { // nothing happens, just handling if a user is pressing a non-number key } }
A nem szám értékeknél dobott NumberFormatException-t csak elkaptam, és jeleztem az esetleges későbbi programozónak, hogy okkal nem írtam a catch ágba semmit, mert egyelőre nem kell oda most más.
Dátumok kezelése
Bár a megrendelések dátumánál már az összes JXDatePicker panel kezdőértékét az aktuális mai dátumra állítottam, a fizetési dátumnál például egyáltalán nem furcsa, ha a dátum nincs megadva. Ez elég gyakran megtörténik egy vadiúj megrendelésnél, mivel azoknál csak a megrendelés dátuma adott, a kifizetésé még egy jövőbeni esemény.
Ezt eddig nagyvonalúan nem néztem meg, most azonban már kezelnem kellett a nem megadott dátum értékeket, mivel az adatbázisba mentés, az onnan kiolvasás miatt egy csomó dátumkonverzió kellett a különböző típusok között.
Ez például:
this.insert.setDate(1, Date.valueOf(company.getLaunch()));
hibát dobott, mert a null-t próbáltam Date típusra konvertálni.
Az összes dátummal foglalkozó kódrészletet átírtam, hogy a null értékeket is kezelni tudjuk:
if (company.getLaunch() != null) { this.insert.setDate(1, Date.valueOf(company.getLaunch())); } else { this.insert.setDate(1, null); }
vagy itt:
if (rs.getDate("launch") != null) { company.setLaunch(rs.getDate("launch").toLocalDate()); } else { company.setLaunch(null); }
Ez ugye különösebb gondot nem okozott, csak át kellett bogarászni a forráskódokat, és az összes dátummal kapcsolatos metódust átírni.
Ablak bezárása
A tesztelés során problémát okozott az is, ha valamelyik dialógus ablakot nem a Mégsem gombbal zártam le, hanem a jobb felső sarokban lévő X-szel. Ha emlékeztek, akkor a Controller osztályokban egy WindowsListenerrel figyeltem, hogy a feldobott dialógusablakot bezártam-e. Erről itt írtam.
modifyDelete.addWindowListener(new WindowAdapter() { public void windowClosed(WindowEvent e) { if (modifyDelete.getSelectedEvent().equals("delete")) { try { deleteCompany(Integer.valueOf(modifyDelete.getSelectedId())); modifyDeleteCompany(); } catch (SQLException ex) { System.out.println("Hiba: " + ex.getMessage()); JOptionPane.showMessageDialog(new javax.swing.JDialog(), "Adatbázishiba: " + ex.getMessage(), "Adatbázishiba", JOptionPane.ERROR_MESSAGE); } } else if (modifyDelete.getSelectedEvent().equals("modify")) { try { modifyCompany(Integer.valueOf(modifyDelete.getSelectedId())); modifyDeleteCompany(); } catch (SQLException ex) { System.out.println("Hiba: " + ex.getMessage()); JOptionPane.showMessageDialog(new javax.swing.JDialog(), "Adatbázishiba: " + ex.getMessage(), "Adatbázishiba", JOptionPane.ERROR_MESSAGE); } } } });
A dialógusablak egy String változóba beírta, hogy melyik gombbal zártam be őt, ha a Küldés gombbal, akkor a String a „modify”, ha a törlés gombbal, akkor a „delete” értéket kapta. Az értékadások nélkül a String null állapotban maradt.
Az ablak X-szel történő bezárásakor aktiválódott a windowClosed metódus, és a történet ott szállt el, amikor a getSelectedEvent() által visszaadott null értéket összehasonlítottam a „delete” stringgel.
Ezt sokféle képpen meg lehetett volna oldani, én az egész blokk elé betettem egy feltételvizsgálatot, mely megnézte, hogy nem-e null állapotban maradt a String változóm, azaz nem-e X-szel zártam be az ablakot.
modifyDelete.addWindowListener(new WindowAdapter() { public void windowClosed(WindowEvent e) { if (modifyDelete.getSelectedEvent() != null) { if (modifyDelete.getSelectedEvent().equals("delete")) { try { deleteCompany(Integer.valueOf(modifyDelete.getSelectedId())); modifyDeleteCompany(); } catch (SQLException ex) { System.out.println("Hiba: " + ex.getMessage()); JOptionPane.showMessageDialog(new javax.swing.JDialog(), "Adatbázishiba: " + ex.getMessage(), "Adatbázishiba", JOptionPane.ERROR_MESSAGE); } } else if (modifyDelete.getSelectedEvent().equals("modify")) { try { modifyCompany(Integer.valueOf(modifyDelete.getSelectedId())); modifyDeleteCompany(); } catch (SQLException ex) { System.out.println("Hiba: " + ex.getMessage()); JOptionPane.showMessageDialog(new javax.swing.JDialog(), "Adatbázishiba: " + ex.getMessage(), "Adatbázishiba", JOptionPane.ERROR_MESSAGE); } } } } });
A catch ágakban mindenhol szerepel egy feldobott hibaablak az adatbázishiba esetére, és egy konzolra történő kiíratás. Utóbbit a lefordított szoftver felhasználója nem fogja látni, ezeket magamnak írogattam be most a tesztelésre, de nem baj, ha benne marad. A későbbiekben is módosítom majd a kódot, az user úgysem látja, nekem segítség lehet.
Nem érdemes eltrehánykodni a kivételkezelést sem
Csak érdekességként írom le, mert ide illik, és én is megtanultam a leckét: a kivételkezelést sem érdemes eltrehánykodni. A végleges szoftver 26 együttműködő fájlból áll, és nagyon sok helyen van adatbázisművelet, ami ugye SQLException kivételt dobhat.
Mivel a NetBeans addig nem futtatja a kódot, míg a kivételek nincsenek kezelve, ezért a kismillió helyen én így oldottam meg a kivételkezelést:
} catch (SQLException ex) { System.out.println("Hiba: " + ex.getMessage()); }
Ami szerintem tök rendben volt, mivel kiírtam a hibaüzenetet is. Aztán kódolás közben valamit elrontottam, és a rendszer kiírta a fenti hibaüzenetet. Ezzel csak az volt a baj, hogy a 26 osztály mindegyik metódusa ugyanezt az üzenetet dobta volna, így nem lettem tőle okosabb. Át kellett írnom az összes osztályt, és pontosabb kivételkezelést beállítani, ahol megmondtam, éppen melyik osztályban vagyunk. Így a szoftvert lefuttatva már nem csak a fenti általános hibaüzenetet kaptam, hanem a Java megmondta, melyik osztályban volt a hiba. A javítása 3 másodperc volt, a kivételkezelések hibaüzenetének pótlása kb. 40 perc 🙂
Új ügyél/áfa/megrendelés/vállalat/dolgozó hozzáadása
A Controller osztályok kezelik az új egyed hozzáadását is. A CustomerController például így adta hozzá az új ügyfelet:
public void addNewCustomer() throws SQLException { CustomerDetailsDialog customerDetails = new CustomerDetailsDialog(frame, true); customerDetails.setVisible(true); customerDetails.addWindowListener(new WindowAdapter() { public void windowClosed(WindowEvent e) { if (customerDetails.getSelectedButton() != null) { if (customerDetails.getSelectedButton().equals("send") && customerDetails.getErrorMessage() == null) { try { customerRepo.save(new Customer(customerDetails.getName(), customerDetails.getZip(), customerDetails.getCity(), customerDetails.getStreet(), customerDetails.getHouse(), customerDetails.getTaxnumber(), customerDetails.getRegnumber(), customerDetails.getContact(), customerDetails.getEmail(), customerDetails.getWebsite())); } catch (SQLException ex) { System.out.println("Hiba: " + ex.getMessage()); JOptionPane.showMessageDialog(new javax.swing.JDialog(), "Adatbázishiba: " + ex.getMessage(), "Adatbázishiba", JOptionPane.ERROR_MESSAGE); } } } } }); }
Feldobott egy CustomerDetailsDialog űrlapot, amiben szépen fel lehetett vinni az ügyfél adatokat a megfelelő mezőkbe:
Itt is egy figyelőt állítottam szolgálatba, a Küldés gomb megnyomása egy „send” értéket írt bele a selectedButton String változóba, a Mégsem gomb pedig egy „cancel” értéket.
Ugye itt is le kellett kezelnem azt az esetet, ha a felhasználó a jobb felső sarokban lévő X-szel zárta be az ablakot, így az egész blokkot egy feltételbe foglaltam, azaz megnéztem, hogy egyáltalán megnyomták-e valamelyik gombot:
if (customerDetails.getSelectedButton() != null) {
A CustomerDetailsDialog-ban viszont az űrlap becsukása előtt lefuttattam egy ellenőrzést, ami a Küldés gomb megnyomása után futott le, és ha hibát talált, akkor feldobott egy figyelmeztető ablakot, és nem engedte elküldeni az űrlapot. Ezzel két dolgot is megoldottam, egyrészt a kötelezőnek választott mezőket mindenképpen ki kellett tölteni, másrészt a hibás adatokat nem adtam át az adatbázisba mentéshez, hanem javításra kértem fel az usert:
Az erre szolgáló metódus pedig a CustomerDetailsDialog esetén így néz ki:
private boolean fieldCheck() { boolean back = true; String errorMessage = ""; if (nameField.getText().equals("")) { back = false; errorMessage += "- Az ügyfél neve mező nem lehet üres.\n"; } if (!emailField.getText().equals("")) { if (!emailField.getText().contains("@")) { back = false; errorMessage += "- Az email cím hibásnak tűnik, mert nem tartalmaz @ jelet.\n"; } } if (!back) { this.errorMessage = "A következő hibákat találtam a kitöltés közben: \n"; this.errorMessage += errorMessage; } return back; }
Lehetne mindenféle dolgot ellenőrizni, de mivel ez a szoftver saját célra lesz, így csak a minimális feltételeket vizsgáltam. Például, legalább egy fő adat mindenhol kell, ez ügyfélnél a név, dolgozónál a név, de például az ÁFA típusnál kell az érték is. Ha a felhasználó extra adatokat is megad, azokat leellenőrzöm, például, ha beír email címet, akkor annak már tartalmaznia kell egy kukac jelet is. De például az irányítószám és a házszám sem numerikus érték, utóbbinál a lépcsőház és ajtó részlet miatt, az előbbinél pedig a külföldi ügyfeleim miatt, akiknél az irányítószám betűt is tartalmaz. Ezeket az elveket mindegyik űrlapnál betartottam, és főleg a numerikus mezőket ellenőriztem, nehogy elszálljon a program nem numerikus értékek bevitele miatt.
Itt pedig a CustomerDetailsDialog űrlap Küldés gombjának figyelője, amin látszik, hogy az űrlap nem csukódhat be, amíg a fieldCheck még hibát talál:
private void sendButtonActionPerformed(java.awt.event.ActionEvent evt) { selectedButton = "send"; if (!fieldCheck()) { JOptionPane.showMessageDialog(new javax.swing.JDialog(), errorMessage, "Hiba a kitöltés során!", JOptionPane.ERROR_MESSAGE); } else { JOptionPane.showMessageDialog(new javax.swing.JDialog(), "Az adatok sikeresen elmentésre kerültek."); this.dispose(); } }
Ha visszatérünk a CustomerController osztályba, a kiegészített addNewCustomer() metódushoz, akkor láthatjuk, mi a hiba:
if (customerDetails.getSelectedButton() != null) { if (customerDetails.getSelectedButton().equals("send")) { try { customerRepo.save(new Customer(customerDetails.getName(), customerDetails.getZip(), customerDetails.getCity(), customerDetails.getStreet(), customerDetails.getHouse(), customerDetails.getTaxnumber(), customerDetails.getRegnumber(), customerDetails.getContact(), customerDetails.getEmail(), customerDetails.getWebsite()));
Miután ellenőriztem, hogy történt gombnyomás, és nem az X-szel zártam be a CustomerDetailsDialog űrlapot, megnéztem, hogy az Elküldés („send”) lett-e megnyomva. Ha igen, akkor lekértem az adatokat, és megpróbáltam adatbázisba menteni az adatokat. Amire az Java hibát dobott.
Mi volt a baj? A megoldásra viszonylag gyorsan rájöttem, hiszen az űrlapot elküldve én megnyomtam a Küldés gombot, így a getSelectedButton már a „send” stringet adta vissza lekérdezéskor. Közben azonban az adatok nem voltak megfelelőek, és a fieldCheck metódus feldobott egy hibaablakot, és nem engedte bezárni az űrlapot. Ettől függetlenül a Küldés gomb megnyomása megtörtént, és a háttérben a CustomerController meghívta a DAO osztály save metódusát, elküldve neki a rossz adatokat, a Java pedig hibaüzenettel leállt.
Ezen a problémát kétféle dologgal segítettem:
A CustomerController már azt is vizsgálta, hogy üres-e a CustomerDetailsDialog űrlap errorMessage változója, mert ha igen, akkor a fieldCheck mindent rendben talált, és biztosan nem dob hibát az adatbáziskezelő rendszer az adatok elmentésekor.
public void addNewCustomer() throws SQLException { CustomerDetailsDialog customerDetails = new CustomerDetailsDialog(frame, true); customerDetails.setVisible(true); customerDetails.addWindowListener(new WindowAdapter() { public void windowClosed(WindowEvent e) { if (customerDetails.getSelectedButton() != null) { if (customerDetails.getSelectedButton().equals("send") && customerDetails.getErrorMessage() == null) { try { customerRepo.save(new Customer(customerDetails.getName(), customerDetails.getZip(), customerDetails.getCity(), customerDetails.getStreet(), customerDetails.getHouse(), customerDetails.getTaxnumber(), customerDetails.getRegnumber(), customerDetails.getContact(), customerDetails.getEmail(), customerDetails.getWebsite())); } catch (SQLException ex) { System.out.println("Hiba: " + ex.getMessage()); JOptionPane.showMessageDialog(new javax.swing.JDialog(), "Adatbázishiba: " + ex.getMessage(), "Adatbázishiba", JOptionPane.ERROR_MESSAGE); } } } } }); }
A CustomerDetailsDialog űrlap fieldCheck metódusa is egy kis kiegészítésre szolgált, hiszen, ha az űrlap kitöltésekor nem talált hibát, akkor tényleg null errorMessage értéket tudott lekérdezni a Controller, de ha a felhasználó hibázott, akkor az errorMessage értéket kapott. Ezután amennyiben a felhasználó javította a hibákat, és a fieldCheck nem talált már problémát, akkor az errorMessage értékét vissza kellett állítania nullára, különben a Controller sosem kapná meg a jelzést az adatbázisba mentésre. És így lett teljes a történet:
private boolean fieldCheck() { boolean back = true; String errorMessage = ""; if (nameField.getText().equals("")) { back = false; errorMessage += "- Az ügyfél neve mező nem lehet üres.\n"; } if (!back) { this.errorMessage = "A következő hibákat találtam a kitöltés közben: \n"; this.errorMessage += errorMessage; } else { this.errorMessage = null; } return back; }
Vélemény, hozzászólás?