Jaka jest „duża liczba” wyjątków do wdrożenia w mojej bibliotece?

20

Zawsze zastanawiałem się, ile różnych klas wyjątków powinienem wdrożyć i wyrzucić dla różnych elementów mojego oprogramowania. Mój szczególny rozwój jest zwykle związany z C ++ / C # / Java, ale wierzę, że jest to pytanie dla wszystkich języków.

Chcę zrozumieć, jaka jest duża liczba różnych wyjątków i czego społeczność programistów oczekuje od dobrej biblioteki.

Kompromisy, które widzę, obejmują:

  • Więcej klas wyjątków może pozwolić na bardzo dokładne poziomy obsługi błędów dla użytkowników interfejsu API (podatne na konfigurację użytkownika lub błędy danych lub nie znaleziono plików)
  • Więcej klas wyjątków pozwala na umieszczenie w wyjątku informacji specyficznych dla błędu, a nie tylko na ciąg znaków lub kod błędu
  • Więcej klas wyjątków może oznaczać więcej konserwacji kodu
  • Więcej klas wyjątków może oznaczać, że interfejs API jest mniej dostępny dla użytkowników

Scenariusze, w których chciałbym zrozumieć użycie wyjątku, obejmują:

  • Podczas etapu „konfiguracji”, który może obejmować ładowanie plików lub ustawianie parametrów
  • Podczas fazy typu „operacja”, w której biblioteka może wykonywać zadania i wykonywać pewne prace, być może w innym wątku

Inne wzorce raportowania błędów bez użycia wyjątków lub mniej wyjątków (dla porównania) mogą obejmować:

  • Mniej wyjątków, ale osadzanie kodu błędu, którego można użyć jako odnośnika
  • Zwracanie kodów błędów i flag bezpośrednio z funkcji (czasami niemożliwe z wątków)
  • Wdrożono system zdarzenia lub wywołania zwrotnego po błędzie (unika rozwijania stosu)

Co programiści wolą widzieć?

Jeśli istnieją WIELE WYJĄTKÓW, czy i tak niepokoisz się obsługą ich osobno?

Czy preferujesz typy obsługi błędów w zależności od etapu operacji?

Kędziory
źródło
1
być może powiązane: programmers.stackexchange.com/questions/3713/…
Fuzz
5
„Interfejs API jest mniej dostępny dla użytkowników” - można sobie z tym poradzić, dysponując kilkoma klasami wyjątków, które wszystkie dziedziczą po wspólnej klasie bazowej dla interfejsu API. Następnie, gdy zaczynasz, możesz sprawdzić, z którego interfejsu API pochodzi wyjątek, nie martwiąc się o wszystkie szczegóły. Zanim ukończysz swój pierwszy program za pomocą interfejsu API, jeśli potrzebujesz więcej informacji o błędzie, zaczniesz sprawdzać, co tak naprawdę oznaczają różne wyjątki. Oczywiście musisz jednocześnie dobrze grać ze standardową hierarchią wyjątków używanego języka.
Steve Jessop,
istotny punkt Steve
Fuzz

Odpowiedzi:

18

Utrzymuję to w prostocie.

Biblioteka ma podstawowy typ wyjątku rozszerzony z std ::: runtime_error (który z C ++ stosuje się odpowiednio do innych języków). Ten wyjątek pobiera ciąg komunikatu, abyśmy mogli się zalogować; każdy punkt rzucania ma unikalną wiadomość (zwykle z unikalnym identyfikatorem).

O to chodzi.

Uwaga 1 : W sytuacjach, gdy ktoś przechwytujący wyjątek może naprawić wyjątki i wznowić działanie. Dodam pochodne wyjątki dla rzeczy, które mogą być potencjalnie wyjątkowo naprawione w zdalnej lokalizacji. Jest to jednak bardzo rzadkie (pamiętaj, że łapacz raczej nie znajdzie się blisko punktu rzucania, więc naprawienie problemu będzie trudne (ale wszystko zależy od sytuacji)).

Uwaga 2 : Czasami biblioteka jest tak prosta, że ​​nie warto nadawać jej własnego wyjątku i zrobi to std :: runtime_error. Wyjątek jest ważny tylko wtedy, gdy możliwość odróżnienia go od std :: runtime_error może dać użytkownikowi wystarczającą ilość informacji, aby coś z nim zrobić.

Uwaga 3 : W obrębie klasy zwykle wolę kody błędów (ale nigdy nie będą one uciekały przez publiczny interfejs API mojej klasy).

Patrząc na swoje kompromisy:

Kompromisy, które widzę, obejmują:

Więcej klas wyjątków może pozwolić na bardzo dokładne poziomy obsługi błędów dla użytkowników interfejsu API (podatne na konfigurację użytkownika lub błędy danych lub nie znaleziono plików)

Czy więcej wyjątków naprawdę zapewnia lepszą kontrolę ziarna? Pojawia się pytanie, czy kod przechwytujący naprawdę naprawia błąd na podstawie wyjątku. Jestem pewien, że zdarzają się takie sytuacje iw takich przypadkach powinieneś mieć inny wyjątek. Ale wszystkie wyjątki wymienione powyżej jedyną użyteczną poprawką jest wygenerowanie dużego ostrzeżenia i zatrzymanie aplikacji.

Więcej klas wyjątków pozwala na umieszczenie w wyjątku informacji specyficznych dla błędu, a nie tylko na ciąg znaków lub kod błędu

To doskonały powód do korzystania z wyjątków. Ale informacje muszą być przydatne dla osoby, która je buforuje. Czy mogą wykorzystać te informacje do wykonania pewnych działań naprawczych? Jeśli obiekt znajduje się wewnątrz biblioteki i nie można go użyć do wywierania wpływu na interfejs API, informacje są bezużyteczne. Musisz być bardzo konkretny, aby przekazane informacje miały przydatną wartość dla osoby, która może je złapać. Osoba przechwytująca to zwykle znajduje się poza publicznym interfejsem API, więc dostosuj informacje, aby można było z nimi korzystać w publicznym interfejsie API.

Jeśli wszystko, co mogą zrobić, to zarejestrować wyjątek, najlepiej po prostu rzucić komunikat o błędzie zamiast dużej ilości danych. Ponieważ łapacz zwykle buduje komunikat o błędzie z danymi. Jeśli zbudujesz komunikat o błędzie, będzie on spójny we wszystkich łapaczach, jeśli pozwolisz łapaczowi zbudować komunikat o błędzie, możesz otrzymać ten sam błąd zgłaszany inaczej w zależności od tego, kto dzwoni i łapie.

Mniej wyjątków, ale osadzanie kodu błędu, którego można użyć jako odnośnika

Musisz określić pogodę, kod błędu może być użytecznie użyteczny. Jeśli to możliwe, powinieneś mieć własny wyjątek. W przeciwnym razie użytkownicy będą musieli teraz wdrożyć instrukcje switch wewnątrz catch (co przeczy temu, że catch automatycznie obsługuje rzeczy).

Jeśli nie może, to dlaczego nie skorzystać z komunikatu o błędzie w wyjątku (nie trzeba dzielić kodu i komunikatu, że wyszukiwanie jest trudne).

Zwracanie kodów błędów i flag bezpośrednio z funkcji (czasami niemożliwe z wątków)

Zwracanie kodów błędów jest świetne wewnętrznie. Pozwala to naprawić błędy tam i wtedy, i musisz upewnić się, że naprawiłeś wszystkie kody błędów i uwzględniłeś je. Ale przeciekanie ich przez publiczny interfejs API to zły pomysł. Problem polega na tym, że programiści często zapominają sprawdzać stany błędów (przynajmniej wyjątek, niesprawdzony błąd zmusi aplikację do wyjścia z nieobsługiwanego błędu, który ogólnie spowoduje uszkodzenie wszystkich danych).

Wdrożono system zdarzenia lub wywołania zwrotnego po błędzie (unika rozwijania stosu)

Ta metoda jest często stosowana w połączeniu z innym mechanizmem obsługi błędów (nie jako alternatywa). Pomyśl o swoim programie Windows. Użytkownik inicjuje akcję, wybierając element menu. Generuje to akcję w kolejce zdarzeń. Kolejka zdarzeń ostatecznie przypisuje wątek do obsługi akcji. Wątek ma obsłużyć akcję i ostatecznie powrócić do puli wątków i czekać na kolejne zadanie. W tym przypadku wyjątek musi zostać wychwycony u podstawy przez wątek, którego zadaniem jest zadanie. Rezultat wyłapania wyjątku zwykle spowoduje wygenerowanie zdarzenia dla głównej pętli, co ostatecznie spowoduje wyświetlenie użytkownikowi komunikatu o błędzie.

Ale jeśli nie będziesz mógł kontynuować w obliczu wyjątku, stos się rozwinie (przynajmniej dla wątku).

Martin York
źródło
+1; Tho: „W przeciwnym razie użytkownicy będą musieli teraz wdrożyć instrukcje switch wewnątrz catch (co przeczy temu, że catch automatycznie obsługuje rzeczy”). - Odwijanie stosu wywołań (jeśli nadal możesz go używać) i wymuszona obsługa błędów są nadal ogromnymi zaletami. Ale z pewnością pogorszy to zdolność do rozwijania stosu wywołań, gdy musisz złapać go na środku i rzucić.
Merlyn Morgan-Graham
9

Zwykle zaczynam od:

  1. Klasa wyjątków dla błędów argumentów . Np. „Argument nie może być pusty”, „argument musi być dodatni” i tak dalej. Java i C # mają dla nich predefiniowane klasy; w C ++ zwykle tworzę tylko jedną klasę, wywodzącą się ze std :: wyjątek.
  2. Klasa wyjątków dla błędów wstępnych . Są to dla bardziej złożonych testów, takich jak „indeks musi być mniejszy niż rozmiar”.
  3. Klasa wyjątków dla błędów asercji . Służą one do sprawdzania stanu pod kątem metod zgodności w połowie. Na przykład podczas iteracji po liście w celu zliczenia elementów, które są ujemne, zero lub dodatnie, na końcu te trzy powinny zsumować się do rozmiaru.
  4. Jedna podstawowa klasa wyjątków dla samej biblioteki. Na początku po prostu rzuć tę klasę. Dopiero gdy zajdzie taka potrzeba, zacznij dodawać podklasy.
  5. Wolę nie owijać wyjątków, ale wiem, że opinie różnią się w tej kwestii (i są bardzo skorelowane z używanym językiem). Jeśli to zrobisz, będziesz potrzebować dodatkowych klas wyjątków zawijania .

Ponieważ klasy dla pierwszych 3 przypadków są pomocami do debugowania, nie są przeznaczone do obsługi przez kod. Zamiast tego powinny zostać złapane tylko przez program obsługi najwyższego poziomu, który wyświetla informacje tak, aby użytkownik mógł skopiować i wkleić je do programisty (lub jeszcze lepiej: nacisnąć przycisk „wyślij raport”). Dołącz więc informacje przydatne dla programisty: plik, funkcję, numer linii i komunikat, który jasno wskazuje, które sprawdzenie się nie powiodło.

Ponieważ pierwsze 3 przypadki są takie same dla każdego projektu, w C ++ zwykle po prostu kopiuję je z poprzedniego projektu. Ponieważ wielu robi dokładnie to samo, projektanci C # i Java dodali standardowe klasy dla tych przypadków do standardowej biblioteki. [AKTUALIZACJA:] Dla leniwych programistów: jedna klasa może wystarczyć i przy odrobinie szczęścia Twoja standardowa biblioteka ma już odpowiednią klasę wyjątków. Wolę dodawać takie informacje, jak nazwa pliku i numer bielizny, których nie zapewniają domyślne klasy w C ++. [Zakończ aktualizację]

W zależności od biblioteki czwarty przypadek może mieć tylko jedną klasę lub stać się garstką klas. Wolę podejście zwinne niż proste, dodając podklasy, gdy zajdzie taka potrzeba.

Aby uzyskać szczegółową argumentację dotyczącą mojej czwartej sprawy, zobacz odpowiedź Lokiego Astari . Całkowicie zgadzam się z jego szczegółową odpowiedzią.

Sjoerd
źródło
+1; Istnieje wyraźna różnica między tym, jak należy pisać wyjątki w ramce, a tym, jak należy pisać wyjątki w aplikacji. To rozróżnienie jest trochę rozmyte, ale wspominasz o tym (odkładając na Lokiego w przypadku biblioteki).
Merlyn Morgan-Graham