Walczę z bardzo prostym pytaniem:
Pracuję teraz nad aplikacją serwerową i muszę wymyślić hierarchię wyjątków (niektóre wyjątki już istnieją, ale potrzebna jest ogólna struktura). Jak w ogóle zacząć to robić?
Myślę o zastosowaniu tej strategii:
1) Co się dzieje nie tak?
- Pytanie o coś jest niedozwolone.
- Coś jest pytane, jest dozwolone, ale nie działa z powodu złych parametrów.
- Coś jest pytane, jest dozwolone, ale nie działa z powodu błędów wewnętrznych.
2) Kto uruchamia żądanie?
- Aplikacja kliencka
- Kolejna aplikacja serwera
3) Przekazywanie wiadomości: ponieważ mamy do czynienia z aplikacją serwera, chodzi przede wszystkim o odbieranie i wysyłanie wiadomości. A co jeśli wysłanie wiadomości nie powiedzie się?
W związku z tym możemy uzyskać następujące typy wyjątków:
- ServerNotAllowedException
- ClientNotAllowedException
- ServerParameterException
- ClientParameterException
- InternalException (w przypadku, gdy serwer nie wie, skąd pochodzi żądanie)
- ServerInternalException
- ClientInternalException
- MessageHandlingException
Jest to bardzo ogólne podejście do definiowania hierarchii wyjątków, ale obawiam się, że brakuje mi oczywistych przypadków. Czy masz pomysły na obszary, których nie omawiam, czy znasz jakieś wady tej metody lub czy istnieje bardziej ogólne podejście do tego rodzaju pytań (w drugim przypadku, gdzie mogę je znaleźć)?
Z góry dziękuję
źródło
catch
bloków, których używam, wyjątek nie jest większy niż w komunikacie o błędzie. Naprawdę nie mam nic innego, co mogę zrobić dla wyjątku związanego z nieumiejętnością odczytania pliku, ponieważ nie udało się przydzielić pamięci podczas procesu odczytu, więc zwykle wychwytujęstd::exception
i zgłaszam komunikat o błędzie, który on zawiera, może dekorowanie to z"Failed to open file: %s", ex.what()
buforem stosu przed wydrukowaniem.catch
bloków w jednej witrynie odzyskiwania, ale często to po prostu zignorowanie wiadomości w wyjątku i wydrukowanie bardziej zlokalizowanej wiadomości ...Odpowiedzi:
Uwagi ogólne
(nieco stronniczy)
Zwykle nie wybrałbym szczegółowej hierarchii wyjątków.
Najważniejsze: wyjątek mówi dzwoniącemu, że metoda nie zakończyła zadania. Twój rozmówca musi zostać o tym powiadomiony, aby nie mógł kontynuować. Działa to z każdym wyjątkiem, bez względu na wybraną klasę wyjątku.
Drugim aspektem jest logowanie. Chcesz znaleźć sensowne wpisy w dzienniku, gdy coś pójdzie nie tak. To także nie potrzebuje różnych klas wyjątków, tylko dobrze zaprojektowane wiadomości tekstowe (przypuszczam, że nie potrzebujesz automatu do odczytu dzienników błędów ...).
Trzecim aspektem jest reakcja twojego rozmówcy. Co może zrobić Twój rozmówca, gdy otrzyma wyjątek? W tym przypadku sensowne mogą być różne klasy wyjątków, aby osoba dzwoniąca mogła zdecydować, czy ponowić to samo połączenie, użyć innego rozwiązania (np. Zamiast tego użyć źródła rezerwowego), czy zrezygnować.
A może chcesz wykorzystać swoje wyjątki jako podstawę do poinformowania użytkownika końcowego o problemie. Oznacza to utworzenie przyjaznej dla użytkownika wiadomości oprócz tekstu administratora dla pliku dziennika, ale nie potrzebuje różnych klas wyjątków (choć może to może ułatwić generowanie tekstu ...).
Ważnym aspektem rejestrowania (i komunikatów o błędach użytkownika) jest możliwość zmiany wyjątku za pomocą informacji kontekstowych poprzez przechwycenie go na pewnej warstwie, dodanie niektórych informacji kontekstowych, np. Parametrów metody, i ponowne ich wyrzucenie.
Twoja hierarchia
Kto uruchamia żądanie? Nie sądzę, że będziesz potrzebować informacji, kto uruchomił żądanie. Nie potrafię sobie nawet wyobrazić, skąd znasz to głęboko w stosie wywołań.
Obsługa wiadomości : to nie jest inny aspekt, ale dodatkowe przypadki „Co się dzieje nie tak?”.
W komentarzu mówisz o flagi „ bez logowania ” podczas tworzenia wyjątku. Nie sądzę, że w miejscu, w którym tworzysz i zgłaszasz wyjątek, możesz podjąć wiarygodną decyzję, czy zarejestrować ten wyjątek.
Jedyną sytuacją, jaką mogę sobie wyobrazić, jest to, że jakaś wyższa warstwa używa interfejsu API w sposób, który czasami powoduje wyjątki, a ta warstwa wie, że nie musi niepokoić żadnego administratora wyjątkiem, więc dyskretnie połyka wyjątek. Ale to zapach kodu: oczekiwany wyjątek sam w sobie jest sprzecznością, wskazówką do zmiany interfejsu API. I powinna decydować wyższa warstwa, a nie kod generujący wyjątki.
źródło
Najważniejszą rzeczą, o której należy pamiętać przy projektowaniu wzorca reakcji na błąd, jest upewnienie się, że jest on użyteczny dla osób dzwoniących. Ma to zastosowanie niezależnie od tego, czy używasz wyjątków, czy zdefiniowanych kodów błędów, ale ograniczymy się do zilustrowania wyjątkami.
Jeśli Twój język lub środowisko już udostępnia ogólne klasy wyjątków, używaj ich tam, gdzie są odpowiednie i gdzie można się ich spodziewać . Nie definiuj własnych
ArgumentNullException
aniArgumentOutOfRange
wyjątkowych klas. Dzwoniący nie będą oczekiwać ich złapania.Zdefiniuj
MyClientServerAppException
klasę podstawową, aby uwzględnić unikalne błędy w kontekście aplikacji. Nigdy nie rzucaj instancji klasy podstawowej. Niejednoznaczna odpowiedź na błąd to NAJGORSZA RZECZY . Jeśli występuje „błąd wewnętrzny”, wyjaśnij, na czym polega ten błąd.W przeważającej części hierarchia poniżej klasy podstawowej powinna być szeroka, a nie głęboka. Musisz go pogłębić tylko w sytuacjach, w których jest to przydatne dla dzwoniącego. Na przykład, jeśli istnieje 5 powodów, dla których komunikat może zawieść między klientem a serwerem, możesz zdefiniować
ServerMessageFault
wyjątek, a następnie zdefiniować klasę wyjątku dla każdego z 5 błędów poniżej. W ten sposób osoba dzwoniąca może po prostu złapać nadklasę, jeśli potrzebuje lub chce. Spróbuj ograniczyć to do konkretnych, rozsądnych przypadków.Nie próbuj definiować wszystkich swoich klas wyjątków, zanim zostaną faktycznie użyte. Skończysz powtarzając większość z nich. Jeśli napotkasz przypadek błędu podczas pisania kodu, zdecyduj, jak najlepiej opisać ten błąd. Idealnie powinien być wyrażony w kontekście tego, co próbuje zrobić osoba dzwoniąca.
W związku z poprzednim punktem pamiętaj, że tylko dlatego, że używasz wyjątków do reagowania na błędy, nie oznacza to, że musisz używać tylko wyjątków dla stanów błędów. Pamiętaj, że zgłaszanie wyjątków jest zwykle drogie, a koszt wydajności może się różnić w zależności od języka. W niektórych językach koszt jest wyższy w zależności od głębokości stosu wywołań, więc jeśli błąd występuje głęboko w stosie wywołań, sprawdź, czy nie możesz użyć prostych typów podstawowych (kody błędów całkowitych lub flagi boolowskie) do wypchnięcia błąd tworzy kopię zapasową stosu, dzięki czemu można go rzucić bliżej wywołania dzwoniącego.
Jeśli rejestrujesz jako część odpowiedzi na błąd, wywołującym powinno być łatwe dołączanie informacji kontekstowych do obiektu wyjątku. Zacznij od miejsca, w którym informacje są wykorzystywane w kodzie logowania. Zdecyduj, ile informacji jest potrzebnych, aby dziennik był użyteczny (nie będąc gigantyczną ścianą tekstu). Następnie pracuj wstecz, aby upewnić się, że klasy wyjątków mogą być łatwo dostarczone z tymi informacjami.
Wreszcie, chyba że twoja aplikacja całkowicie poradzi sobie z błędem braku pamięci, nie próbuj radzić sobie z tymi lub innymi katastrofalnymi wyjątkami. Po prostu pozwól, aby system operacyjny sobie z tym poradził, ponieważ w rzeczywistości to wszystko, co możesz zrobić.
źródło
Cóż, poleciłbym przede wszystkim utworzenie
Exception
klasy podstawowej dla wszystkich sprawdzonych wyjątków, które mogą być zgłaszane przez twoją aplikację. Jeśli twoja aplikacja została wywołanaDuckType
, utwórzDuckTypeException
klasę podstawową.To pozwala złapać każdy wyjątek
DuckTypeException
klasy podstawowej do obsługi. Odtąd twoje wyjątki powinny rozgałęziać się z opisowymi nazwami, które lepiej podkreślą rodzaj problemu. Na przykład „DatabaseConnectionException”.Wyjaśnię, że należy sprawdzić wszystkie wyjątki w sytuacjach, które mogą się zdarzyć z wdziękiem w twoim programie. Innymi słowy, nie możesz połączyć się z bazą danych, więc
DatabaseConnectionException
rzucany jest znak, który możesz następnie złapać, aby poczekać i spróbować ponownie po pewnym czasie.Byś nie widzieć sprawdzonej wyjątek dla bardzo nieoczekiwanym problemem, takich jak nieprawidłowy zapytania SQL lub wyjątku pustego wskaźnika, i chciałbym zachęcić was do niech te wyjątki przekroczyć większość klauzul catch (lub złapany i rethrown jak jest to konieczne), aż po przybyciu na główną sam kontroler, który może następnie przechwycić dane
RuntimeException
wyłącznie do celów logowania.Osobiście wolę nie powracać do niezaznaczonego
RuntimeException
jako innego wyjątku, ponieważ ze względu na charakter niesprawdzonego wyjątku nie można się tego spodziewać, a zatem ponowne zwrócenie go pod innym wyjątkiem ukrywa informacje. Jeśli jednak taka jest twoja preferencja, nadal możesz złapaćRuntimeException
i rzucić,DuckTypeInternalException
który w przeciwieństwie do tegoDuckTypeException
wywodzi sięRuntimeException
i dlatego nie jest zaznaczony.Jeśli wolisz, możesz podzielić swoje wyjątki na podkategorie do celów organizacyjnych, takich jak
DatabaseException
wszelkie związane z bazą danych, ale nadal zachęcam do takich wyjątków, aby wywodziły się z twojego wyjątku podstawowegoDuckTypeException
i były abstrakcyjne, a zatem uzyskiwane z wyraźnymi opisowymi nazwami.Zasadniczo przechwytywanie prób powinno być coraz bardziej ogólne, gdy przesuwasz się w górę stosu wywołań do obsługi wyjątków, i ponownie, w głównym kontrolerze, nie poradzisz sobie z,
DatabaseConnectionException
a raczej z prostą,DuckTypeException
z której wywodzą się wszystkie sprawdzone wyjątki.źródło
Spróbuj to uprościć.
Pierwszą rzeczą, która pomoże ci myśleć w innej strategii jest: złapanie wielu wyjątków jest bardzo podobne do użycia sprawdzonych wyjątków z Javy (przepraszam, nie jestem programistą C ++). Nie jest to dobre z wielu powodów, więc zawsze staram się ich nie używać, a twoja strategia wyjątków w hierarchii pamięta mnie bardzo dużo.
Polecam więc inną elastyczną strategię: używaj niezaznaczonych wyjątków i błędów w kodzie.
Na przykład zobacz ten kod Java:
I twój wyjątkowy wyjątek:
Strategia ta znalazłem na ten link i można znaleźć realizację Java tutaj , gdzie można zobaczyć więcej szczegółów na ten temat, ponieważ powyższy kod jest uproszczona.
Ponieważ musisz oddzielić różne wyjątki między aplikacjami „klient” i „inny serwer”, możesz mieć wiele klas kodów błędów implementujących interfejs ErrorCode.
źródło
Wyjątki to nieograniczone gotos i należy je stosować z rozwagą. Najlepszą strategią dla nich jest ich ograniczenie. Funkcja wywołująca musi obsłużyć wszystkie wyjątki zgłaszane przez funkcje, które wywołuje lub program umiera. Tylko funkcja wywołująca ma poprawny kontekst do obsługi wyjątku. Posiadanie funkcji w górę drzewa poleceń wywołuje je bez ograniczeń.
Wyjątki nie są błędami. Występują, gdy okoliczności uniemożliwiają programowi ukończenie jednej gałęzi kodu i wskazują inną gałąź, aby mogła nastąpić.
Wyjątki muszą znajdować się w kontekście wywoływanej funkcji. Na przykład: funkcja rozwiązująca równania kwadratowe . Musi obsługiwać dwa wyjątki: dzielenie przez zero i kwadratowy katalog_główny_negatywnej_numeru. Ale te wyjątki nie mają sensu dla funkcji wywołujących to narzędzie do rozwiązywania równań kwadratowych. Dzieje się tak ze względu na metodę stosowaną do rozwiązywania równań i po prostu ich powtórne odsłanianie elementów wewnętrznych i przerywa enkapsulację. Zamiast tego, div_by_zero należy powtórzyć jako not_quadratic i square_root_of_negative_number i no_real_roots.
Nie ma potrzeby hierarchii wyjątków. Wyliczenie wyjątków (które je identyfikują) zgłaszane przez funkcję jest wystarczające, ponieważ funkcja wywołująca musi je obsłużyć. Pozwalanie im na przetwarzanie drzewa połączeń jest gotowym kontekstem (nieograniczonym).
źródło