Obsługa wyjątków

357
views

Tester webaplikacji wchodzi do baru. Zamawia piwo. Zamawia 0 piw. Zamawia 99999999999 piw. Zamawia zlew. Zamawia -1,337 piw. Zamawia 1″> piw. Zamawia aishd78hsdf.

Pewnie większość z was kojarzy ten suchar, ale musiałem go tutaj wrzucić bo jest idealnym wstępem do dzisiejszego tematu :smiley:. Wszędzie tam gdzie dajemy użytkownikowi możliwość wprowadzania danych musimy liczyć się z tym, że może się to niedobrze skończyć dla aplikacji, która jest źle zabezpieczona.

Pierwsza linia walidacji formularzy zwykle pojawia się już w aplikacji frontendowej, sam HTML w wersji 5 posiada kilka wbudowanych funkcji, a dodatkowo najczęściej dopisywane są walidatory w Javascriptcie. Nie jest to jednak wystarczające. Najważniejsza jest walidacja po stronie serwerowej, bo tą frontendową łatwo ominąć.

Złamanie zasad logiki biznesowej powinno zatrzymać cały proces

Jeśli w aplikacji istnieją jakieś założenia i zasady biznesowe, na które mogą mieć wpływ użytkownicy zewnętrzni (np. wprowadzić błędne dane) to wtedy powinniśmy zatrzymać cały proces, który właśnie miał zostać wywołany i wyrzucić odpowiedni wyjątek.

Weźmy jakiś prosty przykład: Jeśli użytkownik chce utworzyć nowy byt w projekcie i musi podać jego nazwę, ale ta nazwa musi zawierać ilość znaków w odpowiednim zakresie (np. minimum 3 i maksymalnie 128), a wprowadzi w formularzu tylko dwa znaki to wtedy doszło do złamania kontraktu logiki biznesowej. Leci wyjątek.

W moim przekonaniu taka „walidacja” powinna odbywać się w niskiej warstwie logiki, a obsługa wyjątków w warstwie infrastruktury, np. w kontrolerze.

W mojej aplikacji utworzyłem klasę abstrakcyjną FieldException, która będzie reprezentowała wyjątki związane z błędnym wypełnieniem pola formularza. Zawiera podstawową wiadomość, a dodatkowo fieldName aby określić, które pole się nie zgadza.

Przykładowym, który rozszerza abstrakcyjną klasę FieldException może być wyjątek dla zbyt długiej wartości, opatrzony nazwą ValueIsTooLong.

Przykład użycia można zaobserwować w klasie BoxName, która zawiera jasne zasady tworzenia obiektu. Parametr konstruktora, musi zawierać odpowiednią ilość znaków, w innym wypadku zostaną rzucone wyjątki ValueIsTooShort lub ValueIsTooLong. Wyjątki zostaną obsłużone w wyższej warstwie aplikacji.

Oddziel wyjątki warstwy logiki od wyjątków warstwy infrastruktury

To prawdopodobnie najważniejsza zasada. Kiedy zamierzamy stosować praktyki opisane powyżej to nie możemy zapomnieć aby obsługa wyjątków inaczej traktowała te, które zostały spowodowane złamaniem kontraktu logiki biznesowej, a tych, które wynikają z błędów aplikacji, np. kiedy serwer bazodanowy przestanie działać i klient rzuci wyjątek. To są dwie różne sprawy i trzeba je zupełnie inaczej obsłużyć.

Poniżej przedstawiam klasę CreateBox, która jest akcją API odpowiedzialną za tworzenie nowego elementu. Najważniejszą rzeczą w tym punkcie jest to aby funkcja action zawsze zwróciła odpowiedź niezależnie od tego co się stanie podczas wykonywania operacji dodawania nowego ‚Boxu’.

W powyższym kodzie w liniach 33-34 odbywa się główna operacja tworzenia nowego ‚Boxu’. Jak widać całość jest opakowana w blok try i catch. W liniach 35 i 37 widoczne są dwa elementy catch. Pierwszy z nich przechwytuje niezgodności związane z logiką aplikacji, a drugi obsługuje wszystkie inne błędy, które mogły wystąpić w systemie.

W 36 linii tworzona jest wiadomość odpowiedzi do której przekazujemy message pochodzące bezpośrednio z wyjątku. Z kolei w linii 39 ustawiamy niezależnie dla rodzaju błędu zawsze jedną wiadomość w stylu ‚Something went wrong, try again.’. Dlaczego? To proste, użytkownik nie musi wiedzieć, a nawet nie powinien co w danym momencie zepsuło się w naszej aplikacji. Takie szczegóły to kwestie dla administratora tego systemu i warto te szczegóły zapisać jakimś loggerem aby można było łatwo debugować co się wydarzyło.

Testy integracyjne

Na sam koniec przedstawiam testy integracyjne naszej akcji, które powinny pokryć każde możliwe zachowanie aplikacji.

Podsumowanie

  1. Nigdy nie zapomnij o walidacji danych wejściowych po stronie serwera.
  2. Złamanie kontraktu logiki biznesowej jest takim samym błędem jak wewnętrzny błąd systemu, należy niezwłocznie przerwać cały proces. Idealnym rozwiązaniem aby to zapewnić są wyjątki.
  3. Nigdy nie traktuj wyjątków wynikających z niezgodnością logiki tak samo jak błędów aplikacji (np. problem połączenia z bazą danych).
  4. Aplikacja musi być zawsze stabilna i odporna na błędy.
  5. Ukrywaj przed użytkownikiem szczegóły błędów aplikacji, ale loguj informacje o nich aby móc szybciej znaleźć i usunąć problemy.