Strona używa cookies (ciasteczek). Dowiedz się więcej o celu ich używania i zmianach ustawień. Korzystając ze strony wyrażasz zgodę na używanie cookies, zgodnie z aktualnymi ustawieniami przeglądarki.    X

Złap mnie, jeśli potrafisz...

Od mojego ostatniego wpisu minęły okrągłe 4 miesiące, od zlotu miesiąc, czas więc najwyższy przystąpić do czynnego pisania. W końcu nikt nie będzie mnie już podejrzewał, że piszę "pod wyjazd" lub w euforii po wyjazdowej. Zdaje się, że to pierwsze symptomy paranoi... Ale przejdźmy do rzeczy...

Niesiony na fali sławy po moich bardziej technicznych wpisach, zachęcony setkami głosów poparcia przystępuje do kolejnego wpisu stricte technicznego. Poniżej krótko o programowaniu z wyjątkami, ekhm, na przykładzie PHP. Wiem, wiem. Programowanie w PHP to oksymoron. Jednak przy okazji rozmowy z kolegą z pracy musiałem przyznać sam sobie - w PHP można naprawdę wiele zdziałać. Mimo, że obiektowość dopychana jest w tym języku kolanem (cytat z któregoś z redaktorów DP), jak buty do walizki przed wyjazdem na wczasy. Niemniej jednak PHP pozostaje bardzo popularnym językiem i dobrze się sprawdza nawet w dużych projektach.

Czym są wyjątki? Wyjątek, jak sama nazwa wskazuje nie powinien się nigdy zdarzyć, ale prawdopodobieństwo zdarzenia się nieprawdopodobnego jest odwrotnie proporcjonalne do pewności, że nigdy się nie zdarzy. A że dobry(programowo) programista zawsze zakłada, że coś takiego może mieć miejsce to jest na to gotowy. W PHP (jak i w wielu innych językach) wprowadzono więc try, catch i throw. Czy spróbuj, złap i rzuć. Zaczniemy od końca.

Każda metoda i funkcja (własna, o tym za moment) może rzucić wyjątek. Po co? A no po to, żeby zapobiec niemożliwemu i spektakularnemu wywaleniu się aplikacji. Bardzo często przywoływany jest tu przykład dzielenia. Teoretycznie kod:

<?php $a = 1/0; ?>

zakończy się warningiem od PHP. I tyle. Tutaj warto wrócić od poprzedniego akapitu. Funkcje PHP (nazwijmy je - natywne) nie rzucają wyjątków. Ubierzmy więc nasze dzielenie w funkcje. Kod wyglądać będzie tak:

<?php function dzielenie($b) { return 1/$b; } echo dzielenie(0); ?>

Kolejny raz dostaniemy warning od PHP. Spróbujemy więc postąpić nieco inaczej. Funkcja dzielenie dodatkowo sprawdzi parametr $b i jeśli jest równy 0 rzuci wyjątkiem. Następnie spróbujemy ten wyjątek przechwycić. Kod jest następujący:

function dzielenie($b) { if($b==0) throw new Exception('Nie wolno dzielić przez zero!'); return 1/$b; } try { echo dzielenie(0); echo dzielenie(1); } catch (Exception $e) { echo $e->getMessage(); }

W odpowiedzi na ten kod otrzymamy informacje, że nie wolno dzielić przez 0. W tym fragmencie już coś się dzieje i choć jest prosty i na pewno każdy go rozumie, to pokuszę się jednak o wytłumaczenie.

Funkcja dzielenie(), jak obiecałem, sprawdza w pierwszej kolejności czy parametr $b nie jest czasem 0. Jeśli jest - rzuca wyjątek. Składania jak widać jest bardzo prosta. Operator throw poprzedza new, czyli wywołanie nowej instancji klasy Exception. Klasa ta jest wbudowana w PHP od nie wiem kiedy, ale chyba od wersji 5. Konstruktor tej klasy przyjmuje kilka parametrów i są to: $message, $code i $previous (w tej kolejności naturalnie). Pierwszy jest oczywisty, drugi zawiera kod błędu (nasz kod, możemy je sobie dowolnie numerować), trzeci oznaczać może poprzedni wyjątek (obiekt). Ten ostatni oczywiście dlatego, że przechwycone wyjątki mogą rzucać wyjątki... Dojdę do tego.

Teraz co się dzieje dalej? Następuje fragment kodu try{}. Kod w tym fragmencie powinien, ale oczywiście nie musi, móc rzucić wyjątek. Jeśli tak się stanie następuje jego przechwycenie i wykonanie kodu w fragmencie catch (Exception $e) {}. Jeśli wyjątek zostanie rzucony przez którykolwiek fragment kodu następują natychmiastowe przerwanie wykonywania kodu i przejście do catch.

Sama klasa wyjątków jest dość prosta. Posiada kilka metod: getMessage(), getType(), getFile(), getLine(), getTrace(), getPrevious(), getTraceAsString(), __toString(). Wszystkie są zdaje się wystarczająco instynktownie zrozumiałe i nie będę ich tłumaczył. Dla wszystkich, którzy mają wątpliwości, że wyjątki są fajne sugeruję wyobrazić sobie jak wygodne może się stać debugowanie aplikacji.

Oczywiście pozostaje odpowiedzieć sobie dlaczego powinienem używać wyjątków, a nie na przykład zwracać FALSE oraz arraya (albo obiekt) zwierający te wszystkie parametry (bo oczywiście jest to wykonalne). Jest kilka powodów.

Po pierwsze - kod dzięki takiemu zastosowaniu staje się o wiele bardziej czytelny. Nie trzeba robić za każdym wywołaniem metody, która może się nie powieść, weryfikacji jej "statusu". Po drugie, doskonale sprawdzają się wyjątki, gdy chcemy zgrabnie rozdzielić warstwę aplikacji "workową" od logicznej. Po trzecie są takie funkcje i metody, które po prostu zwracają boola i wartość FALSE w return jest wartością oczekiwaną a nie wyjątkową (na przykład gdy sprawdzamy, czy istnieje plik).

Fragment kodu catch() {} może zawierać... try{}. Dzięki temu możemy założyć, że nasza metoda typu disaster również się nie powiedzie i odpowiednio na to zareagujemy (przykład - nie udało się zapytanie do bazy danych. Chcemy zapisać sobie wszystko w logu w pliku... jednak nie plik nie istnieje/nie mamy do niego prawa zapisu. Wysyłamy więc (drugi catch) maila o całej sytuacji do siebie).

Kiedy rzucać wyjątki?

Na koniec krótko napiszę, kiedy ja rzucam wyjątki, albo raczej kiedy robią to moje aplikacje (a co! Nie boję się tego słowa!). Rzucają je zawsze. No... prawie zawsze. Jeśli posiadam w swoim kodzie metodę, która dodaje użytkowników do bazy danych, to rzucać wyjątkiem będzie wszystko po drodze. Od połączenia z bazą danych, aż po samą metodę, która robi query. Jeśli jednak metoda sprawdza, czy użytkownik się zalogował poprawnie (więc zrobi selecta do bazy z jego danymi) to rzucać wyjątek będzie wszystko po drodze, poza ostatnią metodą sprawdzającą ilość krotek (lub przyrównującą tę ilość do 1).

Rzucanie wyjątków w sposób, który opisałem to... podstawa. Wyobraźcie sobie co można osiągnąć, gdy z klasy Exception zaczniemy dziedziczyć i stworzymy sobie potężny portfel klas specjalizujących się w wyjątkach na specjalne okazje (wyjątki dla baz danych, obsługi plików, połączeń do webserwisów...).

PS. Jeśli metoda rzuca wyjątek, a jest wywoływana bez try{} i zdarzy się tenże wyjątek kod zostanie przerwany z fatal errorem o nie przechwyconym wyjątku. Chyba, że PHP ignoruje fatal errory...

PS2. Jeśli było, to przepraszam 

programowanie

Komentarze

0 nowych
budda86   9 #1 10.08.2012 17:39

W php jest try...catch, ale nie ma finally. Zawsze mnie to dziwiło. Skoro już kopiowali koncepcję wyjątów, to musieli w kawałku, a nie po całości?

Co do samych wyjątków, to jesteś trochę niekonsekwentny. Na początku piszesz, że "wyjątek, jak sama nazwa wskazuje nie powinien się nigdy zdarzyć", a później, że Twoje aplikacje rzucają je prawie zawsze. Hmm, czyli Twoje aplikacje cały czas robią coś, co nie powinno się nigdy zdarzyć? ;>

"kod dzięki takiemu zastosowaniu staje się o wiele bardziej czytelny"

Ten argument jest trochę na siłę. Ja mógłbym powiedzieć, że kod pełen bloków try...catch, zagnieżdżonych, łapiących różne typy wyjątków, staje się ekstremalnie nieczytelny. Oczywiście to kwestia zachowania umiaru, ale wydaje mi się, że za bardzo chcesz udowodnić pożyteczność wyjątków i używasz nieco wysilonego argumentu.

Ogólnie wpis bardzo OK, powyżej to tylko takie moje zwykłe czepialstwo ;)

alucosoftware   7 #2 10.08.2012 19:03

Wpis ciekawy, gratuluję :)

Niemniej... zachęcasz do wykorzystywania mechanizmu obsługi wyjątków jako prostego mechanizmu sterującego wykonywaniem kodu (nie różniącego się ideowo od goto) i jest to wysoce niepokojące. Nie wspomniałeś o tym jaki wpływ ma wykorzystywanie wyjątków w PHP na wydajność aplikacji.

Semtex   18 #3 11.08.2012 00:45

Dobrze wiedzieć że żyjesz...

  #4 12.08.2012 13:47

Wszystko fajnie ale dla mnie to przerost formy nad treścią. To my sami w kodzie sprawdzamy wszystkie możliwe sytuacje, stosujemy rzutowanie tam gdzie trzeba i filtry. W efekcie przedstawiony mechanizm nie każdemu jest potrzebny...

Tak czy inaczej fajnie ze wpis się pojawił. Pozdrawiam kolegę po fachu.

kwpolska   6 #5 12.08.2012 14:30

przeindentuj kod, a najchetniej zobaczylbym implementacje w jakims ludzkim jezyku programowania (Python2).
---
@alucosoftware:
zaproponuj cos lepszego.

tfl   8 #6 12.08.2012 14:55

@budda86

Piszac, ze rzucaja mialem na mysli, ze maja throw. Nie rzucany jest wyjatek prawie zawsze.

@kwpolska

zaprezentowane intentowanie kodu jest przyjeta przeze mnie kanwa.

budda86   9 #7 12.08.2012 16:16

@tfl
Nie spinaj się tak, tylko rozmawiamy ;) Ja też uważam, że używanie wyjątków do sterowania przepływem programu jest niewłaściwe. Jak sam napisałeś - wyjątek to coś nieoczekiwanego, co nie powinno wystąpić. Ale pomijając nawet semantykę, tak jak napisał alucosoftware - rzucanie wyjątków nie pozostaje bez znaczenia dla wydajności. Nie wiem jak w PHP, ale w Javie wiąże się to z zebraniem całego stosu wywołań, często z numerami wierszy w kodzie, a to trochę kosztuje. Może nie jakoś strasznie dużo, ale zawsze. Dlatego trzeba zachować zdrowy rozsądek.

"zaprezentowane intentowanie kodu jest przyjeta przeze mnie kanwa."

Yyy, a po polsku?

Autor edytował komentarz.
airpl   2 #8 13.08.2012 08:50

To ja jeszcze dodam że można zmusić PHP do rzucania wyjątkami zamiast wyświetlania errorów. Za pomocą funkcji "set_error_handler" możemy wskazać jaka funkcja ma obsługiwać błędy a w funkcji możemy rzucić wyjątkiem.

Odnośnie stwierdzenia "programowanie w PHP to oksymoron" muszę się nie zgodzić. Jasne, że są lepsze języki bardziej obiektowe, bardziej poukładane itp ... ale jeśli ktoś wie jak używać PHP to potrafi z niego wycisnąć naprawdę wiele. Jest cała masa frameworków, ORM'ów z którymi praca to czysta przyjemność i które dają ogromne możliwości.