Hej,
Chciałbym podyskutować o sterowaniu wyjątkami. Jeżeli ktoś wyraził o tym jakąś opinię na forum przy okazji innego tematu, to była ona przeciwna jej stosowania.
Wiadomo że zabawa z wyjątkami jest bardziej kosztowna niż inne rozwiązania, dlatego nie rozmawiajmy o przypadkach gdy wydajność jest krytyczna.
W ostatnich latach bardzo spopularyzowały się webserwisy. Nie tylko jako komunikacja między modułami w dużych aplikacjach, czy między zupełnie obcymi tworami, ale chociażby poprzez spopularyzowanie single page applications. Załóżmy więc że mamy następujący przypadek:
Tworzona jest aplikacja SPA a development frontendu i backendu jest robiony przez 2 inne teamy. Dobrym rozwiązaniem jest opracowanie jednego api dla błędów i stosowanie go wszędzie. Błędy z dowolnej usługi po stronie FO można obsłużyć w ten sam sposób. Załóżmy że zwrotka będzię w stylu:
{
response: {} ,
errorList: [{
message:"",
param1:{},
param2:{}
},{}]
}
Dostajemy albo prawidłowy response, albo listę błędów. W zależności co dostaniemy, możemy wrzucić response do jakiejś funkcji obsługującej lub wyświetlić listę errorów, podświetlić błędne pola etc.
Rozwiązanie 1: Bez wyjątków
Tworzymy obiekt przechowujący listę errorów oraz flagę, czy dalsze przetwarzanie ma sens.
Walidujemy request i obsługujemy w sposób -> 1) jeżeli posiada errory to dodajemy do listy. Jeżeli jakaś walidacja jest krytyczna, to ustawiamy flagę na true.
-> 2) Jeżeli !flaga, to możemy przetwarza dalej. Jeżeli nie, zwracamy listę.
Robimy dalsze obliczenia lub pobieramy coś z bazy.
Na nowych danych wywołujemy 1) a następnie ponownie 2).
Itd.
Zalety:
- Brak sterowania wyjątkami (zaleta czysto ideowa).
- Szybkie
- Możemy dodawać errory niekrytyczne (np. nieprawidłowy format email), do póki będziemy mieli wystarczająco prawidłowych danych do wykonania kolejnych kroków.
Wady:
- Masa spaghetti kodu obsługującego błędy. Duży stosunek kodu niebiznesowego do biznesowego.
Rozwiązanie 2: Z wyjątkami
Każda faza walidacji składa się na:
- Zebranie listy błędów z obecnego etapu.
- Rzucenie wyjątku z tą listą
Następnie w kontrolerze robimy przechwytywanie wyjątku i mapowanie na odpowiedni request. Dodatkowo np. Spring z javy posiada @ControllerAdvice który automatycznie przechwycił by wyjątek, czyli mapowanie wyjątku na response może zostać zrobione w jednym miejscu i wykorzystane w każdym konrolerze. Podejrzewam że każdy sensowny framework z dowolnego języka ma takie coś zaimplementowane.
Zalety:
- Czytelność. Jest bardzo niewiele kodu obsługującego błędy a i tak mamy pewność że obecny zestaw danych pozwala na dalsze przetwarzanie.
- Posiadamy stos wywołań
Wady:
- Wolniejsze wykonanie.
- Brak możliwości agregacji błędów do póki nie wystąpi błąd krytyczny.
Rozwiązanie 3: Z AOP
Nie lubię AOP bo zaciemnia kod, więc jak ktoś chce to niech dopisze sam ;P.
Wrzućmy na tapetę zalety:
R1)
- Pierwsza zaleta tylko dla purystów. Uważam że można wprowadzić wprowadzić byt o nazwie BusinessException i nie będzie to już wyjątek aplikacji, a wyjątek przetwarzania żądania.
- Druga zaleta tak naprawdę nie do końca jest pewna. Jeżeli duża część requestów będzie poprawna, to się okazać że wcale nie jest wolniejsza. Dodatkowo, doliczając narzut samego wywołania przez http, to jaki procent tracimy?
- Trzecia to tak naprawdę jedyna sensowna zaleta moim zdaniem. Chociaż z drugiej strony, nie jest to warte ceny bo w większości wypadków wystarcza walidacja etapami.
R2)
- Pierwsza zaleta - wydaje mi się że to jest najważniejsze w dużych systemach. Eliminacja boilerplate code to podstawa.
- Na pierwszy rzut oka stos może nam się wydać zbędny. Przecież jeżeli to użytkownik wprowadził błędne dane, to po co nam wiedzieć gdzie to poleciało? Otóż, czasem może się okazać że to my mamy babola, i prawidłowe dane potraktujemy jako błędne. Wtedy taki stos pozwala nam szybciej zlokalizować walidację.
Pora na wady:
R1) - Wada nr 1, bardzo krytyczna. Boilerplate code == zło. I to właściwie wyczerpuje temat.
R2) - Wolniejsze wykonie - tutaj za wytłumaczenie możemy przyjąć odwrotność drugiej zalety R1.
- Faktyczny problem, chociaż w większości przypadków dodaje tylko ciut mniejszą wygodę.
Rozwiąznie AOP stworzyło by pewnie masę kodu który ogarnia dobrze jedna osoba która go napisała. Do tego jest dziurawy w kilku miejscach. Nie wierzę w to rozwiązanie, ale chętnie przyjmę naprostowanie jeżeli jestem w błędzie.
Prosiłbym o dyskusję dyskusję w małym stopniu powiązaną z konkretnym językiem. Wiem że w C nie ma wyjątków a w jakimś innym języku to może w ogóle jest super rozwiązanie z syntatic sugar które jest lekiem na całe zło. Tutaj głównym tematem jest sterowanie wyjątkami a coś poza tym może być dodane jako ciekawostka.
Co uważacie na ten temat? Może znacie jakieś lepsze rozwiązania od przedstawionych?