Rzucanie wyjątkami a "zwykłe" zaprojektowanie obsługi błędu

2

Do napisania tego wątku zainspirował mnie post by @TomRiddle - Skracanie kodu php, aczkolwiek ten temat już mi od jakiegoś czasu chodził po głowie. Z góry zaznaczam też, że nie twierdzę, że sposób zaproponowany przez Toma jest zły. Po prostu - każdy ma inne podejście do tematu i chciałem się zapytać, co Wy o tym sądzicie.

Ogólnie to gdzie macie granicę pomiędzy "zwyczajną" obsługą błędu/zdarzenia a rzucaniem wyjątków.

Raczej logiczne jest, że np. w przypadku próby dostępu do niedostępnego pliku czy dzielenia przez zero to wyjątek wydaje się bardziej odpowiedni, ale odnośnie tego, co napisał @TomRiddle - dałoby się to spokojnie załatwić jakimś if który sprawdzi wartość i jeśli jest nieodpowiednia, to podejmie jakieś czynności - wyświetli błąd, przerwie wykonanie skryptu/aplikacji, odpali ponownie procedurę pobrania niepoprawnych danych itp. Tak samo chociażby sytuacja z dzieleniem przez zero - można obsłużyć wyjątek, ale równie dobrze można przed wykonaniem dzielenia sprawdzić, czy dzielnik jest !=0.

Gdzie u Was przebiega granica między wyjątkiem a innym sposobem obsłużenia problematycznej sytuacji?

Żeby nie było - trochę o tym poczytałem, ale nigdzie nie znalazłem jakiejś dobrej porady w tym temacie. Kilka przykładowych linków (wprawdzie one dotyczą PHP, ale zasada jest raczej podobna w innych językach):
https://www.w3bai.com/pl/php/php_exception.html
https://pl.wikibooks.org/wiki/PHP/Wyj%C4%85tki
http://adam.wroclaw.pl/2015/05/wyjatki-kiedy-jak-i-po-co/
https://kursphp.com/rozdzial-6/wyjatki/
http://webmaster.helion.pl/index.php/php-obsluga-wyjatkow
Kiedy rzucić wyjątek?
https://matkaprogramistka.pl/2018/03/16/php-obsluga-wyjatkow/
http://forum.php.pl/lofiversion/index.php/t146581.html
https://stackoverflow.com/que[...]w-to-catch-a-division-by-zero

5


Krytyczne błędy np. niedobór pamięci -> wyjątki
Błędy typu dzielenie przez zero/timeouty z usług itp -> Option, Either, Try etc

3

@Aleksander32: Dzięki za film, obejrzę w wolnej chwili. Niestety, ten film jest o Javie, a mi chodzi o takie bardziej ogólne zasady.

Poza tym piszesz o wyjątkach albo try, a jest jeszcze trzecia opcja o której pisałem - chociażby sprawdzenie przed wykonaniem operacji, która może się wywalić, czy są spełnione warunki (czyli przykładowo - czy dzielnik jest różny od zera).

Albo w podanym przykładzie od @TomRiddle - nie rzucać wyjątku, tylko sprawdzić czy uzyskana odpowiedź się mieści w spodziewanym zakresie i jeśli nie, to podjąć jakieś inne działania. Rzucanie wyjątku nie jest konieczne i właśnie o to mi chodzi - gdzie jest Waszym zdaniem taka granica, kiedy rzucić wyjątek, a kiedy normalnie to oprogramować.

6
cerrato napisał(a):

@Aleksander32: Dzięki za film, obejrzę w wolnej chwili. Niestety, ten film jest o Javie, a mi chodzi o takie bardziej ogólne zasady.

Nie, ten film nie jest o Javie. Jest ogólnych zasadach

Poza tym piszesz o wyjątkach albo try, a jest jeszcze trzecia opcja o której pisałem - chociażby sprawdzenie przed wykonaniem operacji, która może się wywalić, czy są spełnione warunki (czyli przykładowo - czy dzielnik jest różny od zera).

Trzecia opcja jest uproszczoną wersją monady Try. W przypadku monady możesz podjąć decyzję później. W przypadku Exceptiona możesz podjąć działanie później (ale masz ukryte przepływy sterowania). W przypadku prostego if musisz musisz podjąć decyzję teraz. Chyba że wynik opakujesz w monadę (np Option), to wtedy możesz podjąć decyzję później.

gdzie jest Waszym zdaniem taka granica, kiedy rzucić wyjątek, a kiedy normalnie to oprogramować.

Prosta decyzja w Ruscie i Haskellu. Nigdy nie rzucać "wyjątkami" bo to wywala wątek. Skoro w Ruscie i Haskellu się da to w innych językach też będzie się dać

6

Żeby tylko wyjaśnić moją pozycję, skoro zostałem przywołany.

Zaznaczam że to co tutaj piszę to moja subiektywna opinia, i rozumiem że ktoś się może nie zgadzać.

Przedmowa:

Moja odpowiedź we wspomnianym poście jest wynikiem tego jak traktuję wyjątki w programowaniu. Dla mnie, przekonanie że wyjątek, to nie jest element języka (taki jak switch, if, for) tylko sygnał o "crashu"/"nie poprawnej konfiguracji"/"braku zasobu"/(co gorsza)o "błędzie", to miskoncepcja.

@cerrato Podlinkował kilka artykułów nt wyjątków, przeczytałem je wszystkie i chciałbym poruszyć ten artykuł stąd: http://adam.wroclaw.pl/2015/05/wyjatki-kiedy-jak-i-po-co/ Autor pisze w nim o tym, że według niego wyjątki powinny być używane tylko do przerwania wywołania programu kiedy wydarzy się sytuacja niezależna od programu (libka, plik, zasoby, system, network, etc.). Pisze też bezpośrednio:

Używamy wyjątku do wykrycia zupełnie normalnej sytuacji – znalezienia poszukiwanej wartości w drzewie. Bez problemu można ten kod napisać zwracając znalezioną wartość. Wyjątki tylko zaciemniają sprawę.
Wniosek – nie używaj wyjątków do kontroli przepływu programu.

Ja się z tym zupełnie nie zgadzam, i jest to dla mnie sygnał że autor artykułu myśli o wyjątkach dokładnie w myśli miskoncepcji - tzn. wyjątki == "crash"/"niepoprawna konfiguracja"/"brak zasobu".

Lata programowania imperatywnego i warstwowego, oraz silny wpływ frameworków przyzwyczaił nas do utożsamiania "błędów" z wyjątkami. Moim zdaniem warto sobie zdawać sprawę z tego, że jeśli jakiś element języka jest często używany w jakimś celu (jak np adnotacje do injectowania pól; asercje w testach; klasy do modeli); to nie znaczy to że nie można użyć tych elementów języka w innych celach. Przykładem może być program: "Spróbuj sparsować plik jak HTML, a jeśli się nie da, to zwróć pierwszą linijkę". Z punkty widzenia usera nie ma tu miejsca na sytuacje wyjątkową, a więc wyjątek. Ale można ich użyć. Można zrobić tak że parser html'a rzuca wyjątek, i jest on używany żeby przejść na drugą ścieżkę pobierania pierwszej linijki. Wyjątek więc ten nie znaczy że zaszła sytuacja wyjątkowa na skalę całej aplikacji (jak uważa autor artykułu), ale na skalę parsowania html'a.

Dodam też może co ja (ja - @TomRiddle) rozumiem poprzez słowo "błąd".

  • Kiedy wpiszę złe hasło do bazy danych w .env i zobaczę wielki czerwony stack trace w przeglądarce, dla mnie to nie jest "błąd". To jest informacja od frameworka, że nie nawiązał połączenia z bazą.
  • Kiedy spróbuję stworzyć kontener w dockerze który expose'uje zajęty port, i docker powie mi że nie mogę tego zrobić - again, dla mnie to nie jest "błąd". To jest komunikat od dockera, że ten port jest już otwarty i nie stworzył kontenera na tym porcie. To prawda, JA popełniłem błąd, ale to czym docker odpowiedział błędem nie jest.
  • Kiedy robię request do servera http://server.com/file.html i dostanę kod http 404, to nie jest "błąd 404". To jest status code który mówi mi, że takiego pliku nie ma na serverze.

Natomiast:

  • Kiedy obliczam BMI, i wpiszę 70kg, 172cm wzrostu, a kalkulator powie mi że mam 180 BMI - to jest błąd, innymi słowy "bug". Dla mnie słowa "błąd" i "bug" są synonimami w programowaniu. A wyjątki to wyjątki - element języka.

Wypowiedź

Teraz postaram się odpowiedzieć na Twoje pytanie @cerrato , ale pamiętaj że to tylko moja opinia i mogę się mylić :)

Według mnie, pomysł że można ohandlować od razu "nisko" wyjątkową sytuację, zamiast rzucić wyjątek wynika z troszkę (moim zdaniem) błędnego przekonania, że błędne dane które spowodowały wyjątkową sytuację pochodzą od użytkownika. Takie przekonanie się może brać stąd, że jeśli przykładowo mamy formularz; i użytkownik poda w nim niepoprawne dane - my, jako programiści; mamy zwyczaj myślenia o nich jak o błędach. Że ktoś podał "błędne" dane. Ale tak nie jest. Użytkownik ma prawo wpisać tam co tylko mu się marzy, i my jako programiści, jedyne co możemy mu zrobić to powiedzieć że dla takich danych możemy albo wykonać jakąś akcje (np zapłacić); albo nie możemy z nimi nic zrobić - możemy do tego wyświetlić pole z danymi na czerwono, żeby ułatwić mu ich zmianę. Ale weź pod uwagę; że to nie jest sytuacja wyjątkowa. Należy się spodziewać że użytkownik wpisze "złe dane". Należy mu zwrócić "ładny komunikat" że jego dane są "niepoprawne". I to nie jest obsługa błędów, to nie jest "exception handling". To jest zwykłe działanie programu - przetwarzanie danych; to tylko nasze subiektywne opinie zmuszają nas do myślenia o nich jako o "niepoprawnych". Mamy też (moim zdaniem, znowu) błędne przekonanie że tylko użytkownik może nam podać błędne dane, i tylko takie dane wymagają handlowania. Łatwo stąd dojść do wniosku, że skoro tylko dane od usera mogą być błędne; to tylko taka obsługa błędów ma sens; a zatem poziom ich obsługi (nisko, od razu user-friendly-message; albo wysoko wyjątek+try/catch) nie ma znaczenia. I moim zdaniem to jest punkt wyjścia Twoich rozważań (mogę się mylić).

Ale moim zdaniem, nie zawsze tak jest. Nie zawsze dane które powodują wyjątkową sytuację (coś co wydaje mi się Ty i autor artykułu nazywacie "błąd"), pochodzą od użytkownika. Czasem pochodzą od samego programu.. I właśnie takich sytuacji nie należy handlować nisko. Należy rzucić wyjątek, bo to się nie powinno zdarzyć.

Jednym z tych które podałeś jest np dzielenie przez zero. Owszem, dzielnik mógł pochodzić od użytkownika (np user chciał wyliczyć średnią wieku zespołu który jest pusty), ale równie dobrze może to być pomyłka programisty, które nie wziął tego case'a pod uwagę. Dla nas to jest jasne, i wszyscy widzimy że dla takiego case'a należy rzucić wyjątek, jest to przecież oczywiste (być może dlatego że przyzwyczailiśmy się do tego że takie niskopoziomowe-operacje są cześciej naszym "błędem" niż usera).

Ale z próbą dostępu do nieistniejącego pliku już nie jest to takie oczywiste, linia pomiędzy danymi od usera i danymi z programu się zaciera. Ciężko stwierdzić czy to user popełnił "błąd", czy programista. Dlatego z reguły core'y języków programowania rzucają wyjątek (bo zakładają że to programista się pomylił), ale aplikacje bądź frameworki zwracają default'y (bo zakładają że user się pomylił). Ale to jest tylko ever only an assumption.

Podobne zatarcie linii pojawia się gdy myślimy o aplikacji jako o całości; a nie jako o jego poszczególnych warstwach. Powiedzmy że mamy program, który jak mu dać plik *.pdf, to ma go zamienić na *.html, ale jeśli dać mu *.html to ma go zwrócić bez zmian. Mamy controller UserController (upload pliku) który używa klasy HtmlToPdfConverter. Co powinna zrobić klasa PdfToHtmlConverter kiedy dostanie plik *.html? Patrząc tylko na tę klasę, oczywistym jest że powinna rzucić wyjątek, bo jej odpowiedzialność to zamiana *.pdf na *.html, jeśli dostanie cokolwiek innego niż *.pdf, np *.docx albo właśnie również *.html, powinna rzucić wyjątek (pamiętajmy że to jednak nie jest błąd aplikacji - to po prostu sposób w jaki klasa odpowiedziała na parameter, bo wyjątek to element języka). Ale myśląc o całym programie, wiemy że jeśli user wrzuci *.html, to ma wrócić *.html, więc to może nam zacienić warstwy aplikacji, i może nas przekonać że to może jednak jest spoko że PdfToHtmlConverter po prostu "przepuści *.html", mimo, że w rzeczywistości aplikacja działałaby poprawnie gdyby to UserController "przepuścił" *.html, i wołał PdfToHtmlConverter tylko dla *.pdf.

Pytanie:

  • Czym ryzykujemy rzucając wyjątek nisko i handlując wyjątek wyżej?
    • Cześć ludzi "nie lubi wyjątków"
    • Część ludzi trochę boi się wyjątków.
    • Część ludzi uważa że wyjątki=błędy w myśl zasady "to nie jest błąd aplikacji", więc to nie jest błąd w ogóle, więc wyjątek nie pasuje.
    • Nie złapanie go == crash aplikacji. Przy czym nie złapanie go wynika z nieotestowania tego scenariusza. Tak czy tak, źle.
  • Czym ryzykujemy obsługując błąd nisko?
    • Tym, że błąd być może nie pochodzi o usera, tylko o programisty, mamy buga, i takim obsłużeniem go przemilczymy i tym samym ukryjemy buga.

Kończąc:

  • Są sytuacje wyjątkowe które są spowodowane tylko błędem programisty. Dzielenie przez zero, błędny regexp przy Pattern.compile(), niepoprawny adres requestu http w API, logarytm z 1.
  • Są sytuacje wyjątkowe które są spowodowane tylko "błędem" user'a. Najbardziej popularnym jest niepoprawna walidacja formularza.
  • Jest też masa sytuacji w których źródło problemu nie jest jasne na takiej niskiej warstwie. Wtedy jego ohandlowanie również nie jest jednoznaczne; i najlepszą metodą obsłużenia tego to rzucenie wyjątku; by wyjątek został obsłużony przez wyższą warstwę która wie, czy to pochodzi od usera czy nie; i albo obsłużenie tego ładnie; albo złożenie aplikacji.

Najlepszym sposobem, (moim zdaniem) żeby zobaczyć czy rzucić wyjątek czy go obsłużyć "blisko", jest popatrzenie na nie jednstkowo, blisko, jako klasę. Pomyśleć o klasie jako o jednej rzeczy, jakby była odseparowana od aplikacji całkiem. Pomyśleć, czy gdyby ta klasa była np zewnętrznej libce; gdyby istniała tylko ona; gdyby była dostarczana przez plugin - Czy ona powinna rzucić wyjątek dla takich danych. Czy gdybym miał jej użyć; czy chciałbym żeby ona zrobiła jakiś fallback, jeśli ja się pomylę? Czy chciałbym żeby ona mi powiedziała o tym rzucając wyjątkiem, żebym sam to mógł obsłużyć? Czy wolałbym żeby próbowała mnie, programistę którzy używa klasy, traktować jak użytkownika, którego oczekiwania zawsze są takie same? Cz jeśli napiszę test jednostkowy do tej klasy, to czy to ma sens żeby przyjęła i zwróciła ten sam plik, czy więcej sensu będzie miało jeśli rzuci wyjątek? Czy nie będę chciał użyć tej klasy w miejscu w którym fallback *.html na *.html nie jest dozwolony? Czy chciałbym żeby ta jedna klasa robiła tylko jedną rzecz i robiła ją dobrze, czy powinna ta klasa wiedzieć o użytkowniku i wiedzieć jak obsłużyć jego błąd? A w końcu; czy wyjątek rzucony dla niespodziewanych danych, to faktycznie taka zła rzecz? Czy może jednak to pomocne narzędzie do krojenia klas na miarę i do utrzymania SRP? Jak będziemy tak myśleć o klasach, cała konfuzja zniknie :)

PS: Odpowiadając na pytanie:

Albo w podanym przykładzie od @TomRiddle - nie rzucać wyjątku, tylko sprawdzić czy uzyskana odpowiedź się mieści w spodziewanym zakresie i jeśli nie, to podjąć jakieś inne działania. Rzucanie wyjątku nie jest konieczne i właśnie o to mi chodzi - gdzie jest Waszym zdaniem taka granica, kiedy rzucić wyjątek, a kiedy normalnie to oprogramować.

U mnie, wtedy kiedy jest 1000000% pewności, że błędne dane pochodzą od użytkownika albo od zewnętrznego źródła (jak plik albo network) oraz wtedy kiedy jest biznesowe wymaganie obsłużenia tego. Kiedy jest chociaż cień szansy, że bug jest moim błędem (programisty), wtedy rzucam wyjątek, i handluję go wyżej.

1

Przede wszystkim - wielkie dzięki @tomRiddle za mega obszerną odpowiedź :)

Przykładem może być program: "Spróbuj sparsować plik jak HTML, a jeśli się nie da, to zwróć pierwszą linijkę". Z punkty widzenia usera nie ma tu miejsca na sytuacje wyjątkową, a więc wyjątek

Ja bym to widział inaczej:

  • jeśli się da to przetwórz plik
  • jeśli się nie da to zwróć pierwszą linię
  • jeśli pliku nie ma albo nie da się do niego dostać, to wtedy właśnie mamy tą "sytuację wyjątkową" i możemy rzucić wyjątek, żeby się nam apka nie wysypała.

Przy czym jeszcze trzeba doprecyzować, co masz na myśli pisząc o "punkcie widzenia usera" - czy chodzi o programistę, czy użytkownika aplikacji? Bo user jako użytkownik w ogóle nie powinien wiedzieć, w jaki sposób są różne rzeczy zaimplementowane. On widzi jedynie 3 stany:

  • dostaję widok HTML
  • dostaję jakąś linię dziwnych treści, których się nie dało sparsować
  • dostaję komunikat, że jest jakiś błąd - np. brak pliku do przetworzenia

Można zrobić tak że parser html'a rzuca wyjątek, i jest on używany żeby przejść na drugą ścieżkę pobierania pierwszej linijki.

Ale można to zrobić bez wyjątków - na zasadzie if (!udaloSieSparsowac) uruchomDruguSposob(). I głównie o to mi chodziło w moim pytaniu - czemu jedna albo druga droga by miała być lepsza, czemu w razie jakiejś sytuacji problematycznej (ale jednak typowej i przewidywalnej, a nie jakieś przekroczenie zakresu, brak pamięci itp) lepiej/gorzej jest rzucić wyjątek, a nie po prostu odpalić procedurę awaryjną

Kiedy obliczam BMI, i wpiszę 70kg, 172cm wzrostu, a kalkulator powie mi że mam 180 BMI - to jest błąd, innymi słowy "bug".

A ja to widzę zupełnie inaczej - dla mnie błędem by było, jakby się takie obliczanie BMI wywaliło z czymś w stylu "proces wykonał niedozwoloną operację i zostanie zamknięty", albo dało odpowiedź w stylu "Twój BMi to zielony". Jeśli podaje błędną wartość to może i jest bug, ale po stronie logiki. Aplikacja wykonuje się poprawnie, ale po prostu jej wynik jest niezgodny z oczekiwaniami. Ale nie jest to dla mnie typowy błąd, który się ogarnia wyjątkiem, tylko trzeba sprawdzić dlaczego otrzymane wartości są z tyłka.
Poza tym zauważ, że wyjątek jest w sytuacji, która pojawia się nagle i wiąże się z czymś niezależnym - brak pliku, dzielenie przez zero itp. Pomijając ręczne ich rzucanie, w wielu miejscach mogą one być rzucane z automatu (jakbyś podzielił to swoje BMI przez zero). A to, że wynik obliczeń będzie nieprawidłowy, nie spowoduje tego, że samo się coś rzuci. Co najwyżej Ty możesz porównać otrzymaną odpowiedź z tym, co Twoim zdaniem jest dopuszczalne i samemu rzucić wyjątek. Ale równie dobrze możesz zamiast wyjątku wywołać funkcję badBMIResult(), która zrobi to samo, co Twój wyjątek.

poziom ich obsługi (nisko, od razu user-friendly-message; albo wysoko wyjątek+try/catch) nie ma znaczenia.

No ja właśnie nie jestem taki pewien. Podejrzewam, że są jakieś "zasady sztuki", wytyczne, zalecenia itp, a ja czuję, że mimo poszukiwań nie znalazłem odpowiedzi, gdzie przebiega granica i kiedy lepiej stosować dane podejście. I nie ukrywam, że mnie to męczy. Bo z jednej strony często staram się unikać wyjątków i zrobić to "zwyczajnie" - czyli if (niepoprawnaWartosc) wyswietlKomunikat(), ale z drugiej strony obawiam się, że jest to trochę amarotskie. Ale nie chcę też pójść w drugą stronę i przekombinować poprzez owyjątkowanie wszystkiego co się da, stworzyć tzw. exceptions driven development ;)

Czasem pochodzą od samego programu.. I właśnie takich sytuacji nie należy handlować nisko. Należy rzucić wyjątek, bo to się nie powinno zdarzyć.

Ale co za różnica czy:

  1. dane pochodzą od usera albo z innego źródła? Poza tym, że usera można poprosić o ponowne wprowadzenie, a inne źródło co najwyżej można odpytać ponownie
  2. pomijając sytuację, w której brak obsługi wyjątku spowoduje wywalenie się apki na plecy (oraz np. pisanie jakiegoś frameworka, w którym chcę przekazać obsługę wyjątku w inne miejsce - np. bezpośrednio do apki), co za różnica, czy takie coś obsłużę w ramach wyjątku, czy procedury awaryjnej odpalonej w sytuacji, w której stwierdzę, że pobrane dane są niezgodne z oczekiwaniami?

Ciężko stwierdzić czy to user popełnił "błąd", czy programista. Dlatego z reguły core'y języków programowania rzucają wyjątek (bo zakładają że to programista się pomylił), ale aplikacje bądź frameworki zwracają default'y (bo zakładają że user się pomylił). Ale to jest tylko ever only an assumption.

Ale co to zmienia? Tak czy siak - jest jakiś problem. Dla mnie ważniejsze jest to, żeby problem został obsłużony - a czy w postaci wyjątku, czy przez ręczne odpalenie jakiejś funkcji, to raczej sprawa drugorzędna. Dlatego powtarzam pytanie - czym się różni reakcja w takiej sytuacji za pomocą wyjątku od reakcji "standadrowej" (np. przerwanie obliczeń, zapisanie błędu w logu, wyświetlenie komunikatu o błędzie itp.)?

Co powinna zrobić klasa PdfToHtmlConverter kiedy dostanie plik .html? Patrząc tylko na tę klasę, oczywistym jest że powinna rzucić wyjątek, bo jej odpowiedzialność to zamiana .pdf na .html, jeśli dostanie cokolwiek innego niż .pdf, np .docx albo właśnie również .html, powinna rzucić wyjątek (pamiętajmy że to jednak nie jest błąd aplikacji - to po prostu sposób w jaki klasa odpowiedziała na parameter, bo wyjątek to element języka)

A można jeszcze inaczej - dodać jakiegoś pośrednika, który sprawdzi co to za plik. Jeśli HTML to go zwróci, jeśli PDF to przekaże do konwersji. W ten sposób wiemy, że obiekt konwertujący HTML na PDF zawsze dostanie to, czego mu do szczęścia potrzeba, więc w ogóle można w takim przypadku, za pomocą drobnej zmiany w logice, wyciąć potrzebę rzucania wyjątków albo obsługi sytuacji nietypowych (przynajmniej w zakresie radzenia sobie z typem pliku).

by wyjątek został obsłużony przez wyższą warstwę która wie, czy to pochodzi od usera czy nie; i albo obsłużenie tego ładnie; albo złożenie aplikacji

To brzmi sensownie - przekazywanie informacji między warstwami. Zresztą o tym pisałem chwilę wcześniej - gdy wspomniałem o pisaniu framweorków i delegowaniu reakcji na zdarzenie pomiędzy apką a biblioteką. Ale w sumie można (chociaż to jest trochę prowizorka) zwrócić informację jako wynik funkcji - albo realny wynik, albo kod błędu - czyl analogia do rzucenia wyjątku. Coś w stylu error_get_last(); - https://www.php.net/manual/en/function.error-get-last.php

U mnie, wtedy kiedy jest 1000000% pewności, że błędne dane pochodzą od użytkownika albo od zewnętrznego źródła (jak plik albo network) oraz wtedy kiedy jest biznesowe wymaganie obsłużenia tego. Kiedy jest chociaż cień szansy, że bug jest moim błędem (programisty), wtedy rzucam wyjątek, i handluję go wyżej.

To jest chyba najbliżej odpowiedzi, jakiej oczekiwałem :D
Tylko co w sytuacji, kiedy nie masz aż tak rozbudowanych warstw, kilka wywołań funkcji i nic poza tym? Jakaś prosta apka. Czy jest sens pchać tam wyjątki? Nie jest to overengineering?

3

swoją frustrację kiedyś wylałam w wątku wyjątki kompletnie bez sensu
tam poruszyłam jeszcze jeden problem czyli łapanie wszystkiego, zakładanie że to jest właśnie ten wyjątek którego tu ocezkujemy i nawet nie rzucanie reszty z powrotem
uważam wyjątek nie służy do sterowania normalnym przebiegiem programu, no ale przecież nowoczesne jest więc trzeba tego używać (ironia)

2

Ja dorzucę tylko swoje praktyki/przemyślenia. Wyjątki dla mnie są właśnie czymś wyjątkowym. W opisanych sytuacjach (dni miesiaca z Skracanie kodu php oraz parsowanie htmla/zwracania pierwszej linii) - nie mamy sytuacji wyjątkowych. W przypadku dni miesięcy, no nie ma bata, żeby nagle rok miał 13 miesięcy. Jedynie gdzie taki kod może się rozjechać to chyba upgrade php'a, ktory znacząco zmienia zwracane dane z funkcji date. Ale jednak tego typu migracje na nowe wersje i tak robi sie z glowa, czyta changelogi itp itd. Jeśli nagle rok będzie miał 13 msc (a dla emerytów to i 14), to wg mnie żaden wyjątek nie pomoże :)

Stosowanie wyjątków tam gdzie można dać ifa jest troszkę "over-engineering" wg mnie.

Obecnie wyjątki najczęściej stosuję tam, gdzie naprawdę coś może pójść nie tak np: jakieś czasochłonne procesy, commitowanie transakcji, operacje na plikach (upload) itp itd. Najczęściej też, wyjątki pojawiają się u mnie w sytuacji kiedy mam jakiś ciąg akcji (Chain of Command). Mając dużo kodu i zależności, po prostu łatwiej jest sobie opakować coś w try/catch niż przewidywać wszystkie możliwe przypadki.

Czasami wyjątki są wygodne - bo np rzucając UserNotFoundException - możemy szybko to wypluć w jsonie i będzie cacy, ale jednak to nadal niczym nie steruje.

Popieram @cerrato i @Miang - wyjątki nie powinny służyć do sterowania przebiegiem programu.

3

Kiedyś pracowałem w firmie, gdzie kolesiowi nie chciało zmieniać się iluś tam funkcji, żeby zwrócić poprawny typ to sobie go rzucił wyjątkiem i potem łapał :)

4

Zwyczajna obsługa błędów w językach takich jak Java czy Python to rzucenie exceptionów. Tak to kiedyś działało i było to spójne. Niezależnie, czy to parsowanie inta, czy OOM. Niestety to podejście nie działa, bo to od wołającego powinno zależeć jaka operacja jest błędem, a jaka wyjątkiem. Przykładowo parsując regex znany na etapie kompilacji najprawdopodobniej chciałbym wywalić działanie aplikacji, gdy jest on niepoprawny, bo nie tego sie spodziewałem. Z drugiej strony podczas parsowania regexu z pliku chciałbym mieć możliwość sprawdzenia, czy dany string jest ok. To, że jakaś metoda może mi rzucić unchecked exception w javie (albo jakikolwiek exception w języku, który nie ma checked exceptionów) sprawia, że nie szanuję wyjątków, bo nie wiem kiedy mogę się czego spodziewać. Naturalnym więc wydaje się szukanie alternatywnych podejść jak te np. z monadą Try, przez co mamy ogromny miszmasz nie pozwalający na napisanie czegokolwiek w spójny sposób. Dobrym podejściem jest Rust, albo Go: wszystkie błędy są jawne, ale w razie czego mamy mechanizm zwinięcia całego wątku, który pozwala mi na nie sprawdzanie sytuacji, które według mnie nie powinny się wydarzyć

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