Używanie nie swoich wyjątków do własnych celów. Dobry zwyczaj nie pożyczaj?

0

Piszę aplikację bazodanową. W niektórych sytuacjach (użytkownik zmienia jakiś obiekt, ale obiekt ten w międzyczasie został usunięty z bazy) chcę rzucić wyjątek, że nie znaleziono danego obiektu.
Aktualnie robię to tak, że rzucam NotFoundException z pakietu javassist, który służy do "bytecode engineering". Więc tematycznie pakiet tak średnio pasuje, ale sam opis tego wyjątku jest ok "Signals that something could not be found".

Pytanie: czy takie używanie wyjątków, wyjęte z kontekstu pakietu do którego należą może ewentualnie mieć jakieś negatywne skutki, może jest to mylące lub uważane za złą praktykę?

//mowa o tym wyjątku https://jboss-javassist.github.io/javassist/html/index.html?javassist/NotFoundException.html

1

Wyjątek należący do innej biblioteki możesz rzucać wtedy, kiedy biblioteka wprost na to zezwala, i w miejscach, które są do tego przewidziane. Jeśli biblioteka nie zezwala jawnie, nie powinieneś tak robić.

Przykład 1:

Biblioteka X pozwala Tobie zarejestrować Twój handler do czegośtam i oczekuje, że w razie problemów Twój handler rzuci wyjątek Y. Jest to napisane w dokumentacji itd. - w takim wypadku możesz w tym konkretnym miejscu, w tej konkretnej sytuacji stworzyć i rzucić wyjątek Y.

Przykład 2:

Biblioteka X posiada wyjątek NotFoundException. Ty piszesz aplikację bazodanową i stwierdzasz, że fajnie pasuje Ci nazwa do Twojego problemu - w takim wypadku nie powinieneś używać tego wyjątku.

Naprawiałem kiedyś problem spowodowany tym, że kod klienta rzucał jakiś wyjątek należący do biblioteki thirdparty, totalnie oderwany od kontekstu. Problem polegał na tym, że ta biblioteka też sobie w kodzie objętym przez try... catch... coś robiła i też mogła ten wyjątek rzucić. Dla kodu klienta był przewidziany inny wyjątek, z inną obsługą, a efekt był taki, że jeśli ktoś wpadł w tę sytuację, dostawał HTTP 500 zamiast HTTP 4xx. Może to wyglądać na kosmetyczny problem, ale tak nie było, bo GUI inaczej reagowało na oba typy błędów. Użytkownik zamiast dostać informacje o tym, co i jak ma poprawić, dostawał komunikat, że system się wysypał.

2

To jest działanie na bardzo krótką metę. Twoja aplikacja wiąże się niepotrzebnie z pakietem java assist. Kiedyś może to przeszkadzać. Trzeba zmierzać w kierunku loosely coupled, czyli jak najmniej powiązań, zależności.

0

Dodatkowo ten wyjątek jest checked więc dochodzi obsługa go gdzieś. Myślę, że w tym przypadku lepiej jest albo napisać własny "biznesowy" wyjątek dziedziczący po RuntimeException (i zakładając, że piszesz aplikację webową) złapać go gdzieś na górze z i zwrócić z odpowiednim kodem (404) w Response albo skorzystać np. ze springowego (JdbcTemplate, Data ) i korzystać z DataAccessException albo jakiegoś potomka.

2

W roku 2018 używanie wyjątków checked(biznesowych) to taki sobie pomysł. Nie jest zupełnie zły, ale warto rozważyć użycie Either z VAVr i w ten sposób zwracać problemy.

0

Po co wyjątek skoro jest Optional, ewentualnie wspominany Either? ¯\_(ツ)_/¯

Co do samego temu wydaję mi się, że lepiej używać swoich wyjątków, łatwiej potem się połapać jak coś się wysypie

0

Stworzyłem już wczoraj swój RowNotFoundException.

Piszę aplikację desktopową, Hibernate + JavaFX.

Wygląda to tak: przed wykonaniem update/delete sprawdzam, czy obiekt jest w bazie (mógł zostać usunięty). Jeżeli jest to wykonuję na nim operację, a jak nie to rzucam wyjątek. Wyjątek leci z DAO.java przez AbcModel.java do AbcController.java i w kontrolerze wyświetlam użytkownikowi komunikat o błędzie.

Ze względu na rodzaj operacji (nie zwracam obiektu) Optional mi nie pomoże, co innego przy odczytywaniu z bazy. Z Either chyba podobnie, czy coś mylę? Chyba nie chodzi o pakowanie ewentualnego błędu w Optionala? :D
Z drugiej strony sprawdzenie czy obiekt nie został usunięty z bazy to właśnie jego odczyt metodą get.

Tak wygląda DAO.java

    public static void delete(Indexed object) throws RowNotFoundException {//indexed to interfejs

        if (checkIfExistsById(object.getClass(), object.getId())) {
            Session session = HibernateSessionFactory.openSession();
            Transaction tr = session.beginTransaction();
            session.delete(object);
            tr.commit();
            session.close();
        } else {
            throw new RowNotFoundException(object.getClass() + " object:" + object + " does not exist in database.");
        }
    }

    private static boolean checkIfExistsById(Class c, Integer id) {//teraz widze, ze mozna tu uproscic przekazujac po prostu obiekt, na razie mniejsza z tym :P
        Object object = new Object();
        Session session = HibernateSessionFactory.openSession();
        Object obj = session.get(c, id);
        session.close();
        return obj != null;
    }
6

Metody void to świnie. Każda kryje jakieś ryzyko błędu, ale niestety nie jest ich łatwo uniknąć (często nie warto).
Jeśli zwracasz void to w zasadzie zabawa w Either czy Optional nie ma sensu, i tak nikt wyniku nie sprawdzi, podobnie jak to będzie boolean.
Optionale, Eithery czy nawet boolean działają jak wywołujący metodę musi jakoś skorzystać z wyniku. Tu sztucznie wrzucony Either będzie pewnie olany.

Dlatego: Exception to dobre rozwiązanie w tym przypadku, (jeśli chcemy jakoś zmusić do obsługi).

Jest natomiast więcej problemów... bo void to świnia i ten kod ma problemy.
Jeśli będzie więcej wątków( bo to jakiś serwer) lub więcej programów podłączonych do bazy danych to nie zadziała zabezpieczenie.
**check **i **delete **są w osobnych transakcjach. Sprawdzenie powinno być w jednej sesji/trransakcji inaczej obiekt może być usunięty w innym wątku tuż po sprawdzeniu. Kupa.
Co więcej nawet jak będzie w jednej sesji... to połączenie/baza musi spełniać poziom izolacji REPEATABLE READ, żeby faktycznie zabezpieczenie z check działało.

Co więcej - kasowanie z bazy deletem to często słaby pomysł ogólnie. W projektach biznesowych się tego raczej unika (po iluś latach człowiek uczy się, że UPDATE też się unika, ale to kolejny poziom).

Jak to w ogóle zrobić dobrze?
Generalnie jeśli zależy nam na tym, żeby nie dało się skasować oibiektu, którego nie ma w bazie... to wystarczy, że ten obiekt będzie parametrem metody **delete **, drugim parametrem metody będzie sesja!

Sprawdzanie dodatkowo trzeba zrobić w tej samej sesji/transakcji więc albo przekaż session do metody check, albo... skorzystaj z session.delete(String quyery), w tym drugim przypadku możesz sprawdzić ILE faktycznie obiektów skasowano!

To praktycznie wystarczy. Jeśli to jest zwykła Encja hibernatowa, to ktoś albo ją wyciągnie z bazy danych (w tej samej sesji) i poda do delete (wtedy wiadomo, że obiekt istniał i git)Albo ktoś złośliwie utworzy *ręcznie *i poda do delete, wtedy na złośliwość można po prostu odpowiedzieć RuntimeException -bo to bardzo nietypowa sytuacja, błąd programisty.

Można pójść nawet dalej i parametrem metody utworzyć obiekt, który NA PEWNO zostął właśnie wyciągnięty z bazy danych i nie da się go inaczej stworzyć. (Zrobić wrapper na entity). Ale to raczej ostrożność potrzebna przy dużych projektach zespołowych.

0

Dobra uwaga :)
Jeszcze tylko pytanie:

skorzystaj z session.delete(String quyery)

Nie widzę tej funkcji :P Chodzi o session.creqteQuery(String query) + query.executeUpdate()?

Czyli zostaję przy wyjątku, i oczywiście babol (niemały zresztą) do poprawy.

1 użytkowników online, w tym zalogowanych: 0, gości: 1